diff --git a/covjsonkit/decoder/Position.py b/covjsonkit/decoder/Position.py index 1fc5a5f..4331582 100644 --- a/covjsonkit/decoder/Position.py +++ b/covjsonkit/decoder/Position.py @@ -109,9 +109,6 @@ def to_xarray(self): dims = ["latitude", "longitude", "levelist", "number", "datetime", "t"] ds = [] - # Get coordinates for all domains - all_coords = self.get_domains() - unique_coords = set() # To track unique coordinate tuples unique_domains = [] # To store unique domains @@ -131,14 +128,17 @@ def to_xarray(self): unique_domains.append(domain) # Add to unique domains all_coords = unique_domains - param_values = {} - # Initialize parameter values for all parameters - for parameter in self.parameters: - param_values[parameter] = [] + num = [] + datetime = [] + for coverage in self.covjson["coverages"]: + num.append(coverage["mars:metadata"]["number"]) + datetime.append(coverage["mars:metadata"]["Forecast date"]) + nums = list(set(num)) + datetime = list(set(datetime)) # Process each coordinate domain - for domain_idx, coords in enumerate(all_coords): + for coords in all_coords: dataarraydict = {} x = coords["axes"][self.x_name]["values"] y = coords["axes"][self.y_name]["values"] @@ -147,69 +147,69 @@ def to_xarray(self): steps = [step.replace("Z", "") for step in steps] steps = pd.to_datetime(steps) - num = [] - datetime = [] - for coverage in self.covjson["coverages"]: - num.append(coverage["mars:metadata"]["number"]) - datetime.append(coverage["mars:metadata"]["Forecast date"]) + cov_idx_list = self._find_coverages(nums, datetime, x, y, z) - nums = list(set(num)) - datetime = list(set(datetime)) + coords = { + "latitude": x, + "longitude": y, + "levelist": z, + "number": nums, + "datetime": datetime, + "t": steps, + } - # Extract parameter values for the current domain for parameter in self.parameters: - if len(param_values[parameter]) <= domain_idx: - param_values[parameter].append([]) - - for i, num in enumerate(nums): - if len(param_values[parameter][domain_idx]) <= i: - param_values[parameter][domain_idx].append([]) - - for j, date in enumerate(datetime): - if len(param_values[parameter][domain_idx][i]) <= j: - param_values[parameter][domain_idx][i].append([]) - - for k, step in enumerate(steps): - for coverage in self.covjson["coverages"]: - if ( - coverage["mars:metadata"]["number"] == num - and coverage["mars:metadata"]["Forecast date"] == date - and coverage["domain"]["axes"][self.x_name]["values"] == x - and coverage["domain"]["axes"][self.y_name]["values"] == y - and coverage["domain"]["axes"][self.z_name]["values"] == z - ): - param_values[parameter][domain_idx][i][j] = coverage["ranges"][parameter]["values"] + param_values = [[[] for _ in range(len(datetime))] for _ in range(len(nums))] - for parameter in self.parameters: - param_coords = { - "latitude": x, - "longitude": y, - "levelist": z, - "number": nums, - "datetime": datetime, - "t": steps, + # Extract parameter values for the current domain + for i, j, cov in cov_idx_list: + param_values[i][j] = cov["ranges"][parameter]["values"] + + long_name = self.get_parameter_metadata(parameter)["observedProperty"]["id"] + + if long_name == "t": + long_name = "T" # Avoid collision with time dimension 't' + + attrs = { + "type": self.get_parameter_metadata(parameter)["type"], + "units": self.get_parameter_metadata(parameter)["unit"]["symbol"], + "long_name": long_name, } - dataarray = xr.DataArray( - [[[param_values[parameter][domain_idx]]]], - dims=dims, - coords=param_coords, - name=f"{parameter}_domain_{domain_idx}", + dataarraydict[long_name] = ( + dims, + [[[param_values]]], + attrs, ) - dataarray.attrs["type"] = self.get_parameter_metadata(parameter)["type"] - dataarray.attrs["units"] = self.get_parameter_metadata(parameter)["unit"]["symbol"] - dataarray.attrs["long_name"] = self.get_parameter_metadata(parameter)["observedProperty"]["id"] - dataarraydict[dataarray.attrs["long_name"]] = dataarray - - ds.append(xr.Dataset(dataarraydict)) + ds.append(xr.Dataset(data_vars=dataarraydict, coords=coords)) # Combine all DataArrays into a Dataset for mars_metadata in self.mars_metadata[0]: - for dss in ds: - if mars_metadata != "date" and mars_metadata != "step": + if mars_metadata != "date" and mars_metadata != "step": + for dss in ds: dss.attrs[mars_metadata] = self.mars_metadata[0][mars_metadata] if len(ds) == 1: return ds[0] return ds + + def _find_coverages(self, nums, datetime, x, y, z): + """Find coverages matching domain parameters and return with indices.""" + result = [] + for i, num in enumerate(nums): + for j, date in enumerate(datetime): + for coverage in self.covjson["coverages"]: + if self._covers_domain(coverage, num, date, x, y, z): + result.append((i, j, coverage)) + return result + + def _covers_domain(self, coverage, num, date, x, y, z): + """Check if coverage matches the given domain parameters.""" + return ( + coverage["mars:metadata"]["number"] == num + and coverage["mars:metadata"]["Forecast date"] == date + and coverage["domain"]["axes"][self.x_name]["values"] == x + and coverage["domain"]["axes"][self.y_name]["values"] == y + and coverage["domain"]["axes"][self.z_name]["values"] == z + ) diff --git a/covjsonkit/decoder/TimeSeries.py b/covjsonkit/decoder/TimeSeries.py index a4ecfc4..28f5702 100644 --- a/covjsonkit/decoder/TimeSeries.py +++ b/covjsonkit/decoder/TimeSeries.py @@ -139,14 +139,17 @@ def to_xarray(self): unique_domains.append(domain) # Add to unique domains all_coords = unique_domains - param_values = {} - # Initialize parameter values for all parameters - for parameter in self.parameters: - param_values[parameter] = [] + num = [] + datetime = [] + for coverage in self.covjson["coverages"]: + num.append(coverage["mars:metadata"]["number"]) + datetime.append(coverage["mars:metadata"]["Forecast date"]) + nums = list(set(num)) + datetime = list(set(datetime)) # Process each coordinate domain - for domain_idx, coords in enumerate(all_coords): + for coords in all_coords: dataarraydict = {} x = coords["axes"][self.x_name]["values"] y = coords["axes"][self.y_name]["values"] @@ -155,66 +158,46 @@ def to_xarray(self): steps = [step.replace("Z", "") for step in steps] steps = pd.to_datetime(steps) - num = [] - datetime = [] - for coverage in self.covjson["coverages"]: - num.append(coverage["mars:metadata"]["number"]) - datetime.append(coverage["mars:metadata"]["Forecast date"]) + cov_idx_list = self._find_coverages(nums, datetime, x, y, z) - nums = list(set(num)) - datetime = list(set(datetime)) + coords = { + "latitude": x, + "longitude": y, + "levelist": z, + "number": nums, + "datetime": datetime, + "t": steps, + } - # Extract parameter values for the current domain for parameter in self.parameters: - if len(param_values[parameter]) <= domain_idx: - param_values[parameter].append([]) - - for i, num in enumerate(nums): - if len(param_values[parameter][domain_idx]) <= i: - param_values[parameter][domain_idx].append([]) - - for j, date in enumerate(datetime): - if len(param_values[parameter][domain_idx][i]) <= j: - param_values[parameter][domain_idx][i].append([]) - - for k, step in enumerate(steps): - for coverage in self.covjson["coverages"]: - if ( - coverage["mars:metadata"]["number"] == num - and coverage["mars:metadata"]["Forecast date"] == date - and coverage["domain"]["axes"][self.x_name]["values"] == x - and coverage["domain"]["axes"][self.y_name]["values"] == y - and coverage["domain"]["axes"][self.z_name]["values"] == z - ): - param_values[parameter][domain_idx][i][j] = coverage["ranges"][parameter]["values"] + param_values = [[[] for _ in range(len(datetime))] for _ in range(len(nums))] - for parameter in self.parameters: - param_coords = { - "latitude": x, - "longitude": y, - "levelist": z, - "number": nums, - "datetime": datetime, - "t": steps, + # Extract parameter values for the current domain + for i, j, cov in cov_idx_list: + param_values[i][j] = cov["ranges"][parameter]["values"] + + long_name = self.get_parameter_metadata(parameter)["observedProperty"]["id"] + + if long_name == "t": + long_name = "T" # Avoid collision with time dimension 't' + + attrs = { + "type": self.get_parameter_metadata(parameter)["type"], + "units": self.get_parameter_metadata(parameter)["unit"]["symbol"], + "long_name": long_name, } - dataarray = xr.DataArray( - [[[param_values[parameter][domain_idx]]]], - dims=dims, - coords=param_coords, - name=f"{parameter}_domain_{domain_idx}", + dataarraydict[long_name] = ( + dims, + [[[param_values]]], + attrs, ) - dataarray.attrs["type"] = self.get_parameter_metadata(parameter)["type"] - dataarray.attrs["units"] = self.get_parameter_metadata(parameter)["unit"]["symbol"] - dataarray.attrs["long_name"] = self.get_parameter_metadata(parameter)["observedProperty"]["id"] - dataarraydict[dataarray.attrs["long_name"]] = dataarray - - ds.append(xr.Dataset(dataarraydict)) + ds.append(xr.Dataset(data_vars=dataarraydict, coords=coords)) # Combine all DataArrays into a Dataset for mars_metadata in self.mars_metadata[0]: - for dss in ds: - if mars_metadata != "date" and mars_metadata != "step": + if mars_metadata != "date" and mars_metadata != "step": + for dss in ds: dss.attrs[mars_metadata] = self.mars_metadata[0][mars_metadata] if len(ds) == 1: @@ -222,6 +205,27 @@ def to_xarray(self): return ds + def _find_coverages(self, nums, datetime, x, y, z): + """Find coverages that match the given domain parameters (num, date, x, y, z) + and return them along with domain parameter indices.""" + result = [] + for i, num in enumerate(nums): + for j, date in enumerate(datetime): + for coverage in self.covjson["coverages"]: + if self._covers_domain(coverage, num, date, x, y, z): + result.append((i, j, coverage)) + return result + + def _covers_domain(self, coverage, num, date, x, y, z): + """check if coverage matches the given domain parameters (num, date, x, y, z)""" + return ( + coverage["mars:metadata"]["number"] == num + and coverage["mars:metadata"]["Forecast date"] == date + and coverage["domain"]["axes"][self.x_name]["values"] == x + and coverage["domain"]["axes"][self.y_name]["values"] == y + and coverage["domain"]["axes"][self.z_name]["values"] == z + ) + def _to_xarray_no_forecast_date(self): """Convert monthly-means CovJSON (no 'Forecast date' in metadata) to xarray. @@ -233,7 +237,7 @@ def _to_xarray_no_forecast_date(self): """ ds_list = [] - for cov_idx, coverage in enumerate(self.covjson["coverages"]): + for coverage in self.covjson["coverages"]: domain = coverage["domain"]["axes"] x = domain[self.x_name]["values"] y = domain[self.y_name]["values"] @@ -246,15 +250,20 @@ def _to_xarray_no_forecast_date(self): dataarraydict = {} for parameter in self.parameters: values = coverage["ranges"][parameter]["values"] - dataarray = xr.DataArray( - values, - dims=["t"], - coords={"t": steps}, - ) - dataarray.attrs["type"] = self.get_parameter_metadata(parameter)["type"] - dataarray.attrs["units"] = self.get_parameter_metadata(parameter)["unit"]["symbol"] - dataarray.attrs["long_name"] = self.get_parameter_metadata(parameter)["observedProperty"]["id"] - dataarraydict[dataarray.attrs["long_name"]] = dataarray + long_name = self.get_parameter_metadata(parameter)["observedProperty"]["id"] + + if long_name == "t": + long_name = "T" # Avoid collision with time dimension 't' + + attrs = { + "type": self.get_parameter_metadata(parameter)["type"], + "units": self.get_parameter_metadata(parameter)["unit"]["symbol"], + "long_name": long_name, + } + + dataarray = xr.DataArray(values, dims=["t"], coords={"t": steps}, attrs=attrs) + + dataarraydict[long_name] = dataarray coord_dict = dict( latitude=(["latitude"], x), diff --git a/tests/data/test_timeseries_param_t.json b/tests/data/test_timeseries_param_t.json new file mode 100644 index 0000000..99b7f47 --- /dev/null +++ b/tests/data/test_timeseries_param_t.json @@ -0,0 +1,47 @@ +{ + "type": "CoverageCollection", + "domainType": "PointSeries", + "coverages": [ + { + "mars:metadata": { + "Forecast date": "2026-05-04T18:00:00Z", + "number": 1 + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": {"values": [47.5]}, + "longitude": {"values": [8.5]}, + "levelist": {"values": [74]}, + "t": {"values": ["2026-05-04T18:00:00Z"]} + } + }, + "ranges": { + "t": { + "type": "NdArray", + "dataType": "float", + "shape": [1], + "axisNames": ["t"], + "values": [285.6] + } + } + } + ], + "referencing": [ + { + "coordinates": ["latitude", "longitude", "levelist"], + "system": {"type": "GeographicCRS"} + } + ], + "parameters": { + "t": { + "type": "Parameter", + "unit": {"symbol": "K"}, + "observedProperty": { + "id": "t", + "label": {"en": "Temperature"} + } + } + } +} diff --git a/tests/data/test_verticalprofile_param_t.json b/tests/data/test_verticalprofile_param_t.json new file mode 100644 index 0000000..388f08d --- /dev/null +++ b/tests/data/test_verticalprofile_param_t.json @@ -0,0 +1,94 @@ +{ + "type": "CoverageCollection", + "domainType": "VerticalProfile", + "coverages": [ + { + "mars:metadata": { + "class": "od", + "Forecast date": "2026-05-06T18:00:00Z", + "expver": "0001", + "levelist": 100, + "levtype": "pl", + "model": "icon-ch1-eps", + "step": 0, + "stream": "enfo", + "timespan": "none", + "type": "cf", + "number": 0 + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 47.45541763305664 + ] + }, + "longitude": { + "values": [ + 8.560781121253967 + ] + }, + "levelist": { + "values": [ + 100, + 150 + ] + }, + "t": { + "values": [ + "2026-05-06T18:00:00Z" + ] + } + } + }, + "ranges": { + "t": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 2 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 218.3372802734375, + 219.89144897460938 + ] + } + } + } + ], + "referencing": [ + { + "coordinates": [ + "latitude", + "longitude", + "levelist" + ], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84" + } + } + ], + "parameters": { + "t": { + "type": "Parameter", + "description": { + "en": null + }, + "unit": { + "symbol": "K" + }, + "observedProperty": { + "id": "t", + "label": { + "en": "Temperature" + } + } + } + } +} diff --git a/tests/test_decoder_bounding_box.py b/tests/test_decoder_bounding_box.py index d231626..ec23f43 100644 --- a/tests/test_decoder_bounding_box.py +++ b/tests/test_decoder_bounding_box.py @@ -68,7 +68,7 @@ def setup_method(self, method): "domain": { "type": "Domain", "axes": { - "t": {"values": ["2017-01-01T01:00:00"]}, + "t": {"values": ["2017-01-01T00:00:00"]}, "composite": { "dataType": "tuple", "coordinates": ["x", "y", "z"], @@ -190,7 +190,7 @@ def test_bounding_box_domains(self): domain2 = { "type": "Domain", "axes": { - "t": {"values": ["2017-01-01T01:00:00"]}, + "t": {"values": ["2017-01-01T00:00:00"]}, "composite": { "dataType": "tuple", "coordinates": ["x", "y", "z"], @@ -263,7 +263,13 @@ def test_bounding_box_get_values(self): } assert decoder.get_values() == values - # def test_bounding_box_to_xarray(self): - # decoder = BoundingBox.BoundingBox(self.covjson) - # dataset = decoder.to_xarray() - # print(dataset) + def test_bounding_box_to_xarray_param_t(self): + """to_xarray works with param 't' - no collision since dims use 'datetimes'.""" + decoder = BoundingBox.BoundingBox(self.covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'datetimes' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + assert "p" in ds.data_vars + # Time dimension is 'datetimes', not 't' + assert "datetimes" in ds.dims diff --git a/tests/test_decoder_circle.py b/tests/test_decoder_circle.py new file mode 100644 index 0000000..b064793 --- /dev/null +++ b/tests/test_decoder_circle.py @@ -0,0 +1,71 @@ +"""Tests for Circle decoder's to_xarray method.""" + +from covjsonkit.decoder import Circle + + +class TestCircleDecoder: + def setup_method(self): + self.covjson = { + "type": "CoverageCollection", + "domainType": "circle", + "coverages": [ + { + "mars:metadata": { + "class": "od", + "stream": "oper", + "levtype": "pl", + "date": "20170101", + "step": "0", + "number": "0", + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "t": {"values": ["2017-01-01T00:00:00"]}, + "composite": { + "dataType": "tuple", + "coordinates": ["x", "y", "z"], + "values": [[1, 20, 1], [2, 21, 3]], + }, + }, + }, + "ranges": { + "t": { + "type": "NdArray", + "dataType": "float", + "shape": [2], + "axisNames": ["t"], + "values": [264.9, 263.8], + }, + }, + }, + ], + "referencing": [ + { + "coordinates": ["x", "y", "z"], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + }, + } + ], + "parameters": { + "t": { + "type": "Parameter", + "description": "Temperature", + "unit": {"symbol": "K"}, + "observedProperty": {"id": "t", "label": {"en": "Temperature"}}, + }, + }, + } + + def test_circle_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'datetimes'.""" + decoder = Circle.Circle(self.covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'datetimes' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'datetimes', not 't' + assert "datetimes" in ds.dims diff --git a/tests/test_decoder_frame.py b/tests/test_decoder_frame.py index 11b4e94..23ffa25 100644 --- a/tests/test_decoder_frame.py +++ b/tests/test_decoder_frame.py @@ -314,3 +314,13 @@ def test_to_xarray_multi_coverage(self): # MARS metadata from the first coverage is attached as dataset attrs assert ds.attrs["step"] == "0" assert ds.attrs["class"] == "od" + + def test_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'time'.""" + decoder = Frame.Frame(self.covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'time' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'time', not 't' + assert "time" in ds.dims diff --git a/tests/test_decoder_grid.py b/tests/test_decoder_grid.py index 49e4a99..9444253 100644 --- a/tests/test_decoder_grid.py +++ b/tests/test_decoder_grid.py @@ -464,3 +464,26 @@ def test_grid_get_values(self): ], } assert decoder.get_values() == values + + def test_grid_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'datetimes'.""" + # Add a param named 't' to verify no collision + self.covjson["parameters"]["t"] = { + "type": "Parameter", + "description": "Temperature", + "unit": {"symbol": "K"}, + "observedProperty": {"id": "t", "label": {"en": "Temperature"}}, + } + for cov in self.covjson["coverages"]: + cov["ranges"]["t"] = cov["ranges"]["2t"].copy() + + for cov in self.covjson["coverages"]: + cov["mars:metadata"]["Forecast date"] = cov["mars:metadata"]["date"] + + decoder = Grid.Grid(self.covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'datetimes' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'datetimes', not 't' + assert "datetimes" in ds.dims diff --git a/tests/test_decoder_position.py b/tests/test_decoder_position.py new file mode 100644 index 0000000..2f7045a --- /dev/null +++ b/tests/test_decoder_position.py @@ -0,0 +1,25 @@ +"""Tests for Position decoder's to_xarray method.""" + +import json +from pathlib import Path + +from covjsonkit.api import Covjsonkit + + +class TestPositionDecoder: + """Tests for Position decoder to_xarray with param 't' collision.""" + + def test_position_to_xarray_param_t(self): + """to_xarray must not fail when parameter is named 't' (collides with time dim).""" + path = Path(__file__).parent / "data/test_timeseries_param_t.json" + with open(path, "r") as f: + covjson = json.load(f) + + # Change domainType to position to use Position decoder + covjson["domainType"] = "position" + + ds = Covjsonkit().decode(covjson).to_xarray() + + # Parameter 't' should be renamed to 'T' to avoid collision with time dimension + assert "T" in ds.data_vars, f"Expected 'T' in data_vars, got {list(ds.data_vars)}" + assert "t" in ds.dims or "t" in ds.coords, "Time dimension/coord 't' should still exist" diff --git a/tests/test_decoder_shapefile.py b/tests/test_decoder_shapefile.py index b08e4bd..5410d4f 100644 --- a/tests/test_decoder_shapefile.py +++ b/tests/test_decoder_shapefile.py @@ -310,3 +310,13 @@ def test_to_xarray_multi_coverage(self): # MARS metadata from the first coverage is attached as dataset attrs assert ds.attrs["step"] == "0" assert ds.attrs["class"] == "od" + + def test_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'time'.""" + decoder = Shapefile.Shapefile(self.covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'time' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'time', not 't' + assert "time" in ds.dims diff --git a/tests/test_decoder_time_series.py b/tests/test_decoder_time_series.py index f5289d4..80cb0a2 100644 --- a/tests/test_decoder_time_series.py +++ b/tests/test_decoder_time_series.py @@ -1,5 +1,8 @@ # from earthkit import data +import json +from pathlib import Path + from covjsonkit.api import Covjsonkit @@ -286,9 +289,24 @@ def test_timeseries_coordinates(self): print(decoder.get_coordinates()) assert decoder.get_coordinates() == coordinates + def test_timeseries_to_xarray_param_t(self): + path = Path(__file__).parent / "data/test_timeseries_param_t.json" + with open(path, "r") as f: + covjson = json.load(f) + ds = Covjsonkit().decode(covjson).to_xarray() + data_vars = ["T"] + assert all(var in ds.data_vars for var in data_vars) + + def test_timeseries_to_xarray_no_forecast_date_param_t(self): + path = Path(__file__).parent / "data/test_timeseries_param_t.json" + with open(path, "r") as f: + covjson = json.load(f) + covjson["coverages"][0].pop("mars:metadata") + ds = Covjsonkit().decode(covjson).to_xarray() + data_vars = ["T"] + assert all(var in ds.data_vars for var in data_vars) + def test_timeseries_to_xarray(self): - # decoder = Covjsonkit().decode(self.covjson) - # ds = decoder.to_xarray() # print(ds) # print(ds["Temperature"]) # xrds.to_netcdf("timeseries.nc") diff --git a/tests/test_decoder_vertical_profile.py b/tests/test_decoder_vertical_profile.py index c543cf9..09ef4bc 100644 --- a/tests/test_decoder_vertical_profile.py +++ b/tests/test_decoder_vertical_profile.py @@ -1,3 +1,6 @@ +import json +from pathlib import Path + from covjsonkit.api import Covjsonkit @@ -241,9 +244,15 @@ def test_verticalprofile_values(self): } assert decoder.get_values() == values - # def test_verticalprofile_to_xarray(self): - # decoder = Covjsonkit().decode(self.covjson) - # dataset = decoder.to_xarray() - # encoder = Covjsonkit.encoder.VerticalProfile.VerticalProfile("CoverageCollection", "VerticalProfile") - # cov = encoder.from_xarray(dataset) - # print(cov) + def test_verticalprofile_to_xarray_param_t(self): + """to_xarray works with param 't' - no collision since dims use 'time'.""" + path = Path(__file__).parent / "data/test_verticalprofile_param_t.json" + with open(path, "r") as f: + covjson = json.load(f) + decoder = Covjsonkit().decode(covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'time' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'time', not 't' + assert "time" in ds.dims diff --git a/tests/test_decoder_wkt.py b/tests/test_decoder_wkt.py index 8e5776f..280bdb2 100644 --- a/tests/test_decoder_wkt.py +++ b/tests/test_decoder_wkt.py @@ -310,3 +310,13 @@ def test_to_xarray_multi_coverage(self): # MARS metadata from the first coverage is attached as dataset attrs assert ds.attrs["step"] == "0" assert ds.attrs["class"] == "od" + + def test_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'time'.""" + decoder = Wkt.Wkt(self.covjson) + ds = decoder.to_xarray() + + # Param 't' should be in data_vars (no collision with 'time' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'time', not 't' + assert "time" in ds.dims diff --git a/tests/test_xarray_multipoint.py b/tests/test_xarray_multipoint.py index 3373000..4cf8cee 100644 --- a/tests/test_xarray_multipoint.py +++ b/tests/test_xarray_multipoint.py @@ -44,3 +44,23 @@ def test_from_xarray(self): covjson_result["coverages"][0]["ranges"]["2t"]["values"][0] == self.test_covjson["coverages"][0]["ranges"]["2t"]["values"][0] ) + + def test_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'datetimes'.""" + # Add a param named 't' to verify no collision + self.test_covjson["parameters"]["t"] = { + "type": "Parameter", + "description": {"en": "Temperature"}, + "unit": {"symbol": "K"}, + "observedProperty": {"id": "t", "label": {"en": "Temperature"}}, + } + for cov in self.test_covjson["coverages"]: + cov["ranges"]["t"] = cov["ranges"]["2t"].copy() + + decoder_obj = Covjsonkit().decode(self.test_covjson) + ds = decoder_obj.to_xarray() + + # Param 't' should be in data_vars (no collision with 'datetimes' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'datetimes', not 't' + assert "datetimes" in ds.dims diff --git a/tests/test_xarray_path.py b/tests/test_xarray_path.py index de413d7..8717648 100644 --- a/tests/test_xarray_path.py +++ b/tests/test_xarray_path.py @@ -44,3 +44,23 @@ def test_from_xarray(self): covjson_result["coverages"][0]["ranges"]["2t"]["values"][0] == self.test_covjson["coverages"][0]["ranges"]["2t"]["values"][0] ) + + def test_to_xarray_param_t_no_collision(self): + """to_xarray works with param 't' - no collision since dims use 'datetimes'.""" + # Add a param named 't' to verify no collision + self.test_covjson["parameters"]["t"] = { + "type": "Parameter", + "description": {"en": "Temperature"}, + "unit": {"symbol": "K"}, + "observedProperty": {"id": "t", "label": {"en": "Temperature"}}, + } + for cov in self.test_covjson["coverages"]: + cov["ranges"]["t"] = cov["ranges"]["2t"].copy() + + decoder_obj = Covjsonkit().decode(self.test_covjson) + ds = decoder_obj.to_xarray() + + # Param 't' should be in data_vars (no collision with 'datetimes' dim) + assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}" + # Time dimension is 'datetimes', not 't' + assert "datetimes" in ds.dims