Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
71e91e7
added test demonstrating param 't' issue
lmeinen May 6, 2026
806070d
add second test for mars metadata path
lmeinen May 6, 2026
b659172
added param_t test data as separate json
lmeinen May 6, 2026
466c534
extract loop-independent operations
lmeinen May 6, 2026
a3f4dca
use tuple-based construction for Dataset - surfaces 't' collision exp…
lmeinen May 6, 2026
a7ff95d
reorder inner loops to avoid unnecessary iterations
lmeinen May 6, 2026
14cf7ab
merge loops and drop unneeded nested list
lmeinen May 6, 2026
6c605b7
circumvent bug: capitalize param name if 't'
lmeinen May 6, 2026
45c53e3
reorder if statement with loop to avoid unnecessary iterations
lmeinen May 6, 2026
1052ea2
circumvent bug: capitalize param name if 't' in no_forecast_date path
lmeinen May 6, 2026
0b591d7
add param 't' test and fix failing to_xarray decoding
lmeinen May 7, 2026
1dcb3f9
add param 't' test for Position and clean up to_xarray
lmeinen May 7, 2026
4ec7fc1
add param 't' test for Circle
lmeinen May 7, 2026
8229653
add param 't' test for Grid
lmeinen May 7, 2026
0b1d988
add json file for verticalprofile test
lmeinen May 7, 2026
5617d97
add param 't' test for Frame
lmeinen May 7, 2026
81fe543
add param 't' test for Shapefile
lmeinen May 7, 2026
4d29810
add param 't' test for Wkt
lmeinen May 7, 2026
ede8551
add param 't' test for Multipoint
lmeinen May 7, 2026
37b28af
add param 't' test for Path
lmeinen May 7, 2026
86dd20f
fix failing test: incorrect formatting for axis 't'
lmeinen May 7, 2026
b116098
Fix formatting
awarde96 May 7, 2026
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
116 changes: 58 additions & 58 deletions covjsonkit/decoder/Position.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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))
Comment on lines +137 to +138

# 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"]
Expand All @@ -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
)
139 changes: 74 additions & 65 deletions covjsonkit/decoder/TimeSeries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Comment on lines +148 to +149

# 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"]
Expand All @@ -155,73 +158,74 @@ 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 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.

Expand All @@ -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"]
Expand All @@ -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),
Expand Down
47 changes: 47 additions & 0 deletions tests/data/test_timeseries_param_t.json
Original file line number Diff line number Diff line change
@@ -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"}
}
}
}
}
Loading
Loading