covjsonkit 0.2.18__tar.gz → 0.2.19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/PKG-INFO +1 -1
  2. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Position.py +58 -58
  3. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/TimeSeries.py +74 -65
  4. covjsonkit-0.2.19/covjsonkit/version.py +1 -0
  5. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/PKG-INFO +1 -1
  6. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/SOURCES.txt +4 -0
  7. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/pyproject.toml +1 -1
  8. covjsonkit-0.2.19/tests/data/test_timeseries_param_t.json +47 -0
  9. covjsonkit-0.2.19/tests/data/test_verticalprofile_param_t.json +94 -0
  10. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_bounding_box.py +12 -6
  11. covjsonkit-0.2.19/tests/test_decoder_circle.py +71 -0
  12. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_frame.py +10 -0
  13. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_grid.py +23 -0
  14. covjsonkit-0.2.19/tests/test_decoder_position.py +25 -0
  15. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_shapefile.py +10 -0
  16. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_time_series.py +20 -2
  17. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_vertical_profile.py +15 -6
  18. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_wkt.py +10 -0
  19. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_multipoint.py +20 -0
  20. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_path.py +20 -0
  21. covjsonkit-0.2.18/covjsonkit/version.py +0 -1
  22. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.github/ci-hpc-config.yml +0 -0
  23. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.github/workflows/ci.yaml +0 -0
  24. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.github/workflows/label-public-pr.yml +0 -0
  25. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.gitignore +0 -0
  26. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.pre-commit-config.yaml +0 -0
  27. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/ACKNOWLEDGEMENTS.rst +0 -0
  28. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/LICENSE +0 -0
  29. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/MANIFEST.in +0 -0
  30. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/Makefile +0 -0
  31. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/README.md +0 -0
  32. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/Coverage.py +0 -0
  33. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/CoverageCollection.py +0 -0
  34. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/__init__.py +0 -0
  35. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/api.py +0 -0
  36. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/config.py +0 -0
  37. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/__init__.py +0 -0
  38. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/dwd/param.json +0 -0
  39. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/dwd/param_id.json +0 -0
  40. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/dwd/unit.json +0 -0
  41. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/ecmwf/param.json +0 -0
  42. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/ecmwf/param_id.json +0 -0
  43. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/ecmwf/unit.json +0 -0
  44. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/BoundingBox.py +0 -0
  45. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Circle.py +0 -0
  46. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Frame.py +0 -0
  47. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Grid.py +0 -0
  48. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Path.py +0 -0
  49. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Shapefile.py +0 -0
  50. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/VerticalProfile.py +0 -0
  51. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Wkt.py +0 -0
  52. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/__init__.py +0 -0
  53. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/decoder.py +0 -0
  54. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/BoundingBox.py +0 -0
  55. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Circle.py +0 -0
  56. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Frame.py +0 -0
  57. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Grid.py +0 -0
  58. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Path.py +0 -0
  59. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Position.py +0 -0
  60. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Shapefile.py +0 -0
  61. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/TimeSeries.py +0 -0
  62. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/VerticalProfile.py +0 -0
  63. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Wkt.py +0 -0
  64. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/__init__.py +0 -0
  65. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/encoder.py +0 -0
  66. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/param_db.py +0 -0
  67. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/utils.py +0 -0
  68. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/dependency_links.txt +0 -0
  69. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/not-zip-safe +0 -0
  70. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/requires.txt +0 -0
  71. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/top_level.txt +0 -0
  72. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/docs/images/ECMWF_logo.svg.png +0 -0
  73. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/docs/images/Logo_Destination_Earth_Colours.png +0 -0
  74. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/example_config.json +0 -0
  75. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/requirements.txt +0 -0
  76. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/scripts/generate_param_db.py +0 -0
  77. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/setup.cfg +0 -0
  78. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/setup.py +0 -0
  79. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/conftest.py +0 -0
  80. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_coverage1.json +0 -0
  81. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_coverage2.json +0 -0
  82. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_geotiff.json +0 -0
  83. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_multipoint_coverage.json +0 -0
  84. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_path_coverage.json +0 -0
  85. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_timeseries_coverage.json +0 -0
  86. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_timeseries_xyz_coverage.json +0 -0
  87. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_verticalprofile_coverage.json +0 -0
  88. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_verticalprofile_xyz_coverage.json +0 -0
  89. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/geojsontest.py +0 -0
  90. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/requirements_test.txt +0 -0
  91. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_bounding_box.py +0 -0
  92. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_bounding_box_from_polytope.py +0 -0
  93. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_circle_from_polytope.py +0 -0
  94. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_frame.py +0 -0
  95. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_frame_from_polytope.py +0 -0
  96. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_grid.py +0 -0
  97. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_grid_from_polytope.py +0 -0
  98. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_path.py +0 -0
  99. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_path_from_polytope.py +0 -0
  100. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_position_from_polytope.py +0 -0
  101. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_shapefile.py +0 -0
  102. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_shapefile_from_polytope.py +0 -0
  103. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_time_series.py +0 -0
  104. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_time_series_from_polytope.py +0 -0
  105. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_vertical_profile_from_polytope.py +0 -0
  106. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_wkt.py +0 -0
  107. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_wkt_from_polytope.py +0 -0
  108. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_geojson.py +0 -0
  109. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_geotiff.py +0 -0
  110. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_util_merge.py +0 -0
  111. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_ts.py +0 -0
  112. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_vp.py +0 -0
  113. {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: covjsonkit
3
- Version: 0.2.18
3
+ Version: 0.2.19
4
4
  Home-page: https://github.com/ecmwf/covjsonkit
5
5
  Author: ECMWF
6
6
  Author-email: James.Hawkes@ecmwf.int, Adam.Warde@ecmwf.int, Mathilde.Leuridan@ecmwf.int
@@ -109,9 +109,6 @@ class Position(Decoder):
109
109
  dims = ["latitude", "longitude", "levelist", "number", "datetime", "t"]
110
110
  ds = []
111
111
 
112
- # Get coordinates for all domains
113
- all_coords = self.get_domains()
114
-
115
112
  unique_coords = set() # To track unique coordinate tuples
116
113
  unique_domains = [] # To store unique domains
117
114
 
@@ -131,14 +128,17 @@ class Position(Decoder):
131
128
  unique_domains.append(domain) # Add to unique domains
132
129
 
133
130
  all_coords = unique_domains
134
- param_values = {}
135
131
 
136
- # Initialize parameter values for all parameters
137
- for parameter in self.parameters:
138
- param_values[parameter] = []
132
+ num = []
133
+ datetime = []
134
+ for coverage in self.covjson["coverages"]:
135
+ num.append(coverage["mars:metadata"]["number"])
136
+ datetime.append(coverage["mars:metadata"]["Forecast date"])
137
+ nums = list(set(num))
138
+ datetime = list(set(datetime))
139
139
 
140
140
  # Process each coordinate domain
141
- for domain_idx, coords in enumerate(all_coords):
141
+ for coords in all_coords:
142
142
  dataarraydict = {}
143
143
  x = coords["axes"][self.x_name]["values"]
144
144
  y = coords["axes"][self.y_name]["values"]
@@ -147,69 +147,69 @@ class Position(Decoder):
147
147
  steps = [step.replace("Z", "") for step in steps]
148
148
  steps = pd.to_datetime(steps)
149
149
 
150
- num = []
151
- datetime = []
152
- for coverage in self.covjson["coverages"]:
153
- num.append(coverage["mars:metadata"]["number"])
154
- datetime.append(coverage["mars:metadata"]["Forecast date"])
150
+ cov_idx_list = self._find_coverages(nums, datetime, x, y, z)
155
151
 
156
- nums = list(set(num))
157
- datetime = list(set(datetime))
152
+ coords = {
153
+ "latitude": x,
154
+ "longitude": y,
155
+ "levelist": z,
156
+ "number": nums,
157
+ "datetime": datetime,
158
+ "t": steps,
159
+ }
158
160
 
159
- # Extract parameter values for the current domain
160
161
  for parameter in self.parameters:
161
- if len(param_values[parameter]) <= domain_idx:
162
- param_values[parameter].append([])
163
-
164
- for i, num in enumerate(nums):
165
- if len(param_values[parameter][domain_idx]) <= i:
166
- param_values[parameter][domain_idx].append([])
167
-
168
- for j, date in enumerate(datetime):
169
- if len(param_values[parameter][domain_idx][i]) <= j:
170
- param_values[parameter][domain_idx][i].append([])
171
-
172
- for k, step in enumerate(steps):
173
- for coverage in self.covjson["coverages"]:
174
- if (
175
- coverage["mars:metadata"]["number"] == num
176
- and coverage["mars:metadata"]["Forecast date"] == date
177
- and coverage["domain"]["axes"][self.x_name]["values"] == x
178
- and coverage["domain"]["axes"][self.y_name]["values"] == y
179
- and coverage["domain"]["axes"][self.z_name]["values"] == z
180
- ):
181
- param_values[parameter][domain_idx][i][j] = coverage["ranges"][parameter]["values"]
162
+ param_values = [[[] for _ in range(len(datetime))] for _ in range(len(nums))]
182
163
 
183
- for parameter in self.parameters:
184
- param_coords = {
185
- "latitude": x,
186
- "longitude": y,
187
- "levelist": z,
188
- "number": nums,
189
- "datetime": datetime,
190
- "t": steps,
164
+ # Extract parameter values for the current domain
165
+ for i, j, cov in cov_idx_list:
166
+ param_values[i][j] = cov["ranges"][parameter]["values"]
167
+
168
+ long_name = self.get_parameter_metadata(parameter)["observedProperty"]["id"]
169
+
170
+ if long_name == "t":
171
+ long_name = "T" # Avoid collision with time dimension 't'
172
+
173
+ attrs = {
174
+ "type": self.get_parameter_metadata(parameter)["type"],
175
+ "units": self.get_parameter_metadata(parameter)["unit"]["symbol"],
176
+ "long_name": long_name,
191
177
  }
192
- dataarray = xr.DataArray(
193
- [[[param_values[parameter][domain_idx]]]],
194
- dims=dims,
195
- coords=param_coords,
196
- name=f"{parameter}_domain_{domain_idx}",
178
+ dataarraydict[long_name] = (
179
+ dims,
180
+ [[[param_values]]],
181
+ attrs,
197
182
  )
198
183
 
199
- dataarray.attrs["type"] = self.get_parameter_metadata(parameter)["type"]
200
- dataarray.attrs["units"] = self.get_parameter_metadata(parameter)["unit"]["symbol"]
201
- dataarray.attrs["long_name"] = self.get_parameter_metadata(parameter)["observedProperty"]["id"]
202
- dataarraydict[dataarray.attrs["long_name"]] = dataarray
203
-
204
- ds.append(xr.Dataset(dataarraydict))
184
+ ds.append(xr.Dataset(data_vars=dataarraydict, coords=coords))
205
185
 
206
186
  # Combine all DataArrays into a Dataset
207
187
  for mars_metadata in self.mars_metadata[0]:
208
- for dss in ds:
209
- if mars_metadata != "date" and mars_metadata != "step":
188
+ if mars_metadata != "date" and mars_metadata != "step":
189
+ for dss in ds:
210
190
  dss.attrs[mars_metadata] = self.mars_metadata[0][mars_metadata]
211
191
 
212
192
  if len(ds) == 1:
213
193
  return ds[0]
214
194
 
215
195
  return ds
196
+
197
+ def _find_coverages(self, nums, datetime, x, y, z):
198
+ """Find coverages matching domain parameters and return with indices."""
199
+ result = []
200
+ for i, num in enumerate(nums):
201
+ for j, date in enumerate(datetime):
202
+ for coverage in self.covjson["coverages"]:
203
+ if self._covers_domain(coverage, num, date, x, y, z):
204
+ result.append((i, j, coverage))
205
+ return result
206
+
207
+ def _covers_domain(self, coverage, num, date, x, y, z):
208
+ """Check if coverage matches the given domain parameters."""
209
+ return (
210
+ coverage["mars:metadata"]["number"] == num
211
+ and coverage["mars:metadata"]["Forecast date"] == date
212
+ and coverage["domain"]["axes"][self.x_name]["values"] == x
213
+ and coverage["domain"]["axes"][self.y_name]["values"] == y
214
+ and coverage["domain"]["axes"][self.z_name]["values"] == z
215
+ )
@@ -139,14 +139,17 @@ class TimeSeries(Decoder):
139
139
  unique_domains.append(domain) # Add to unique domains
140
140
 
141
141
  all_coords = unique_domains
142
- param_values = {}
143
142
 
144
- # Initialize parameter values for all parameters
145
- for parameter in self.parameters:
146
- param_values[parameter] = []
143
+ num = []
144
+ datetime = []
145
+ for coverage in self.covjson["coverages"]:
146
+ num.append(coverage["mars:metadata"]["number"])
147
+ datetime.append(coverage["mars:metadata"]["Forecast date"])
148
+ nums = list(set(num))
149
+ datetime = list(set(datetime))
147
150
 
148
151
  # Process each coordinate domain
149
- for domain_idx, coords in enumerate(all_coords):
152
+ for coords in all_coords:
150
153
  dataarraydict = {}
151
154
  x = coords["axes"][self.x_name]["values"]
152
155
  y = coords["axes"][self.y_name]["values"]
@@ -155,66 +158,46 @@ class TimeSeries(Decoder):
155
158
  steps = [step.replace("Z", "") for step in steps]
156
159
  steps = pd.to_datetime(steps)
157
160
 
158
- num = []
159
- datetime = []
160
- for coverage in self.covjson["coverages"]:
161
- num.append(coverage["mars:metadata"]["number"])
162
- datetime.append(coverage["mars:metadata"]["Forecast date"])
161
+ cov_idx_list = self._find_coverages(nums, datetime, x, y, z)
163
162
 
164
- nums = list(set(num))
165
- datetime = list(set(datetime))
163
+ coords = {
164
+ "latitude": x,
165
+ "longitude": y,
166
+ "levelist": z,
167
+ "number": nums,
168
+ "datetime": datetime,
169
+ "t": steps,
170
+ }
166
171
 
167
- # Extract parameter values for the current domain
168
172
  for parameter in self.parameters:
169
- if len(param_values[parameter]) <= domain_idx:
170
- param_values[parameter].append([])
171
-
172
- for i, num in enumerate(nums):
173
- if len(param_values[parameter][domain_idx]) <= i:
174
- param_values[parameter][domain_idx].append([])
175
-
176
- for j, date in enumerate(datetime):
177
- if len(param_values[parameter][domain_idx][i]) <= j:
178
- param_values[parameter][domain_idx][i].append([])
179
-
180
- for k, step in enumerate(steps):
181
- for coverage in self.covjson["coverages"]:
182
- if (
183
- coverage["mars:metadata"]["number"] == num
184
- and coverage["mars:metadata"]["Forecast date"] == date
185
- and coverage["domain"]["axes"][self.x_name]["values"] == x
186
- and coverage["domain"]["axes"][self.y_name]["values"] == y
187
- and coverage["domain"]["axes"][self.z_name]["values"] == z
188
- ):
189
- param_values[parameter][domain_idx][i][j] = coverage["ranges"][parameter]["values"]
173
+ param_values = [[[] for _ in range(len(datetime))] for _ in range(len(nums))]
190
174
 
191
- for parameter in self.parameters:
192
- param_coords = {
193
- "latitude": x,
194
- "longitude": y,
195
- "levelist": z,
196
- "number": nums,
197
- "datetime": datetime,
198
- "t": steps,
175
+ # Extract parameter values for the current domain
176
+ for i, j, cov in cov_idx_list:
177
+ param_values[i][j] = cov["ranges"][parameter]["values"]
178
+
179
+ long_name = self.get_parameter_metadata(parameter)["observedProperty"]["id"]
180
+
181
+ if long_name == "t":
182
+ long_name = "T" # Avoid collision with time dimension 't'
183
+
184
+ attrs = {
185
+ "type": self.get_parameter_metadata(parameter)["type"],
186
+ "units": self.get_parameter_metadata(parameter)["unit"]["symbol"],
187
+ "long_name": long_name,
199
188
  }
200
- dataarray = xr.DataArray(
201
- [[[param_values[parameter][domain_idx]]]],
202
- dims=dims,
203
- coords=param_coords,
204
- name=f"{parameter}_domain_{domain_idx}",
189
+ dataarraydict[long_name] = (
190
+ dims,
191
+ [[[param_values]]],
192
+ attrs,
205
193
  )
206
194
 
207
- dataarray.attrs["type"] = self.get_parameter_metadata(parameter)["type"]
208
- dataarray.attrs["units"] = self.get_parameter_metadata(parameter)["unit"]["symbol"]
209
- dataarray.attrs["long_name"] = self.get_parameter_metadata(parameter)["observedProperty"]["id"]
210
- dataarraydict[dataarray.attrs["long_name"]] = dataarray
211
-
212
- ds.append(xr.Dataset(dataarraydict))
195
+ ds.append(xr.Dataset(data_vars=dataarraydict, coords=coords))
213
196
 
214
197
  # Combine all DataArrays into a Dataset
215
198
  for mars_metadata in self.mars_metadata[0]:
216
- for dss in ds:
217
- if mars_metadata != "date" and mars_metadata != "step":
199
+ if mars_metadata != "date" and mars_metadata != "step":
200
+ for dss in ds:
218
201
  dss.attrs[mars_metadata] = self.mars_metadata[0][mars_metadata]
219
202
 
220
203
  if len(ds) == 1:
@@ -222,6 +205,27 @@ class TimeSeries(Decoder):
222
205
 
223
206
  return ds
224
207
 
208
+ def _find_coverages(self, nums, datetime, x, y, z):
209
+ """Find coverages that match the given domain parameters (num, date, x, y, z)
210
+ and return them along with domain parameter indices."""
211
+ result = []
212
+ for i, num in enumerate(nums):
213
+ for j, date in enumerate(datetime):
214
+ for coverage in self.covjson["coverages"]:
215
+ if self._covers_domain(coverage, num, date, x, y, z):
216
+ result.append((i, j, coverage))
217
+ return result
218
+
219
+ def _covers_domain(self, coverage, num, date, x, y, z):
220
+ """check if coverage matches the given domain parameters (num, date, x, y, z)"""
221
+ return (
222
+ coverage["mars:metadata"]["number"] == num
223
+ and coverage["mars:metadata"]["Forecast date"] == date
224
+ and coverage["domain"]["axes"][self.x_name]["values"] == x
225
+ and coverage["domain"]["axes"][self.y_name]["values"] == y
226
+ and coverage["domain"]["axes"][self.z_name]["values"] == z
227
+ )
228
+
225
229
  def _to_xarray_no_forecast_date(self):
226
230
  """Convert monthly-means CovJSON (no 'Forecast date' in metadata) to xarray.
227
231
 
@@ -233,7 +237,7 @@ class TimeSeries(Decoder):
233
237
  """
234
238
  ds_list = []
235
239
 
236
- for cov_idx, coverage in enumerate(self.covjson["coverages"]):
240
+ for coverage in self.covjson["coverages"]:
237
241
  domain = coverage["domain"]["axes"]
238
242
  x = domain[self.x_name]["values"]
239
243
  y = domain[self.y_name]["values"]
@@ -246,15 +250,20 @@ class TimeSeries(Decoder):
246
250
  dataarraydict = {}
247
251
  for parameter in self.parameters:
248
252
  values = coverage["ranges"][parameter]["values"]
249
- dataarray = xr.DataArray(
250
- values,
251
- dims=["t"],
252
- coords={"t": steps},
253
- )
254
- dataarray.attrs["type"] = self.get_parameter_metadata(parameter)["type"]
255
- dataarray.attrs["units"] = self.get_parameter_metadata(parameter)["unit"]["symbol"]
256
- dataarray.attrs["long_name"] = self.get_parameter_metadata(parameter)["observedProperty"]["id"]
257
- dataarraydict[dataarray.attrs["long_name"]] = dataarray
253
+ long_name = self.get_parameter_metadata(parameter)["observedProperty"]["id"]
254
+
255
+ if long_name == "t":
256
+ long_name = "T" # Avoid collision with time dimension 't'
257
+
258
+ attrs = {
259
+ "type": self.get_parameter_metadata(parameter)["type"],
260
+ "units": self.get_parameter_metadata(parameter)["unit"]["symbol"],
261
+ "long_name": long_name,
262
+ }
263
+
264
+ dataarray = xr.DataArray(values, dims=["t"], coords={"t": steps}, attrs=attrs)
265
+
266
+ dataarraydict[long_name] = dataarray
258
267
 
259
268
  coord_dict = dict(
260
269
  latitude=(["latitude"], x),
@@ -0,0 +1 @@
1
+ __version__ = "0.2.19"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: covjsonkit
3
- Version: 0.2.18
3
+ Version: 0.2.19
4
4
  Home-page: https://github.com/ecmwf/covjsonkit
5
5
  Author: ECMWF
6
6
  Author-email: James.Hawkes@ecmwf.int, Adam.Warde@ecmwf.int, Mathilde.Leuridan@ecmwf.int
@@ -65,8 +65,10 @@ tests/conftest.py
65
65
  tests/geojsontest.py
66
66
  tests/requirements_test.txt
67
67
  tests/test_decoder_bounding_box.py
68
+ tests/test_decoder_circle.py
68
69
  tests/test_decoder_frame.py
69
70
  tests/test_decoder_grid.py
71
+ tests/test_decoder_position.py
70
72
  tests/test_decoder_shapefile.py
71
73
  tests/test_decoder_time_series.py
72
74
  tests/test_decoder_vertical_profile.py
@@ -101,6 +103,8 @@ tests/data/test_geotiff.json
101
103
  tests/data/test_multipoint_coverage.json
102
104
  tests/data/test_path_coverage.json
103
105
  tests/data/test_timeseries_coverage.json
106
+ tests/data/test_timeseries_param_t.json
104
107
  tests/data/test_timeseries_xyz_coverage.json
105
108
  tests/data/test_verticalprofile_coverage.json
109
+ tests/data/test_verticalprofile_param_t.json
106
110
  tests/data/test_verticalprofile_xyz_coverage.json
@@ -8,7 +8,7 @@ markers = ["data: uses test data (deselect with '-m \"not data\"')",]
8
8
 
9
9
  [project]
10
10
  name = "covjsonkit"
11
- version = "0.2.18"
11
+ version = "0.2.19"
12
12
  dependencies = [
13
13
  "pandas<3",
14
14
  "orjson",
@@ -0,0 +1,47 @@
1
+ {
2
+ "type": "CoverageCollection",
3
+ "domainType": "PointSeries",
4
+ "coverages": [
5
+ {
6
+ "mars:metadata": {
7
+ "Forecast date": "2026-05-04T18:00:00Z",
8
+ "number": 1
9
+ },
10
+ "type": "Coverage",
11
+ "domain": {
12
+ "type": "Domain",
13
+ "axes": {
14
+ "latitude": {"values": [47.5]},
15
+ "longitude": {"values": [8.5]},
16
+ "levelist": {"values": [74]},
17
+ "t": {"values": ["2026-05-04T18:00:00Z"]}
18
+ }
19
+ },
20
+ "ranges": {
21
+ "t": {
22
+ "type": "NdArray",
23
+ "dataType": "float",
24
+ "shape": [1],
25
+ "axisNames": ["t"],
26
+ "values": [285.6]
27
+ }
28
+ }
29
+ }
30
+ ],
31
+ "referencing": [
32
+ {
33
+ "coordinates": ["latitude", "longitude", "levelist"],
34
+ "system": {"type": "GeographicCRS"}
35
+ }
36
+ ],
37
+ "parameters": {
38
+ "t": {
39
+ "type": "Parameter",
40
+ "unit": {"symbol": "K"},
41
+ "observedProperty": {
42
+ "id": "t",
43
+ "label": {"en": "Temperature"}
44
+ }
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "type": "CoverageCollection",
3
+ "domainType": "VerticalProfile",
4
+ "coverages": [
5
+ {
6
+ "mars:metadata": {
7
+ "class": "od",
8
+ "Forecast date": "2026-05-06T18:00:00Z",
9
+ "expver": "0001",
10
+ "levelist": 100,
11
+ "levtype": "pl",
12
+ "model": "icon-ch1-eps",
13
+ "step": 0,
14
+ "stream": "enfo",
15
+ "timespan": "none",
16
+ "type": "cf",
17
+ "number": 0
18
+ },
19
+ "type": "Coverage",
20
+ "domain": {
21
+ "type": "Domain",
22
+ "axes": {
23
+ "latitude": {
24
+ "values": [
25
+ 47.45541763305664
26
+ ]
27
+ },
28
+ "longitude": {
29
+ "values": [
30
+ 8.560781121253967
31
+ ]
32
+ },
33
+ "levelist": {
34
+ "values": [
35
+ 100,
36
+ 150
37
+ ]
38
+ },
39
+ "t": {
40
+ "values": [
41
+ "2026-05-06T18:00:00Z"
42
+ ]
43
+ }
44
+ }
45
+ },
46
+ "ranges": {
47
+ "t": {
48
+ "type": "NdArray",
49
+ "dataType": "float",
50
+ "shape": [
51
+ 2
52
+ ],
53
+ "axisNames": [
54
+ "levelist"
55
+ ],
56
+ "values": [
57
+ 218.3372802734375,
58
+ 219.89144897460938
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ ],
64
+ "referencing": [
65
+ {
66
+ "coordinates": [
67
+ "latitude",
68
+ "longitude",
69
+ "levelist"
70
+ ],
71
+ "system": {
72
+ "type": "GeographicCRS",
73
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
74
+ }
75
+ }
76
+ ],
77
+ "parameters": {
78
+ "t": {
79
+ "type": "Parameter",
80
+ "description": {
81
+ "en": null
82
+ },
83
+ "unit": {
84
+ "symbol": "K"
85
+ },
86
+ "observedProperty": {
87
+ "id": "t",
88
+ "label": {
89
+ "en": "Temperature"
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
@@ -68,7 +68,7 @@ class TestDecoder:
68
68
  "domain": {
69
69
  "type": "Domain",
70
70
  "axes": {
71
- "t": {"values": ["2017-01-01T01:00:00"]},
71
+ "t": {"values": ["2017-01-01T00:00:00"]},
72
72
  "composite": {
73
73
  "dataType": "tuple",
74
74
  "coordinates": ["x", "y", "z"],
@@ -190,7 +190,7 @@ class TestDecoder:
190
190
  domain2 = {
191
191
  "type": "Domain",
192
192
  "axes": {
193
- "t": {"values": ["2017-01-01T01:00:00"]},
193
+ "t": {"values": ["2017-01-01T00:00:00"]},
194
194
  "composite": {
195
195
  "dataType": "tuple",
196
196
  "coordinates": ["x", "y", "z"],
@@ -263,7 +263,13 @@ class TestDecoder:
263
263
  }
264
264
  assert decoder.get_values() == values
265
265
 
266
- # def test_bounding_box_to_xarray(self):
267
- # decoder = BoundingBox.BoundingBox(self.covjson)
268
- # dataset = decoder.to_xarray()
269
- # print(dataset)
266
+ def test_bounding_box_to_xarray_param_t(self):
267
+ """to_xarray works with param 't' - no collision since dims use 'datetimes'."""
268
+ decoder = BoundingBox.BoundingBox(self.covjson)
269
+ ds = decoder.to_xarray()
270
+
271
+ # Param 't' should be in data_vars (no collision with 'datetimes' dim)
272
+ assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}"
273
+ assert "p" in ds.data_vars
274
+ # Time dimension is 'datetimes', not 't'
275
+ assert "datetimes" in ds.dims
@@ -0,0 +1,71 @@
1
+ """Tests for Circle decoder's to_xarray method."""
2
+
3
+ from covjsonkit.decoder import Circle
4
+
5
+
6
+ class TestCircleDecoder:
7
+ def setup_method(self):
8
+ self.covjson = {
9
+ "type": "CoverageCollection",
10
+ "domainType": "circle",
11
+ "coverages": [
12
+ {
13
+ "mars:metadata": {
14
+ "class": "od",
15
+ "stream": "oper",
16
+ "levtype": "pl",
17
+ "date": "20170101",
18
+ "step": "0",
19
+ "number": "0",
20
+ },
21
+ "type": "Coverage",
22
+ "domain": {
23
+ "type": "Domain",
24
+ "axes": {
25
+ "t": {"values": ["2017-01-01T00:00:00"]},
26
+ "composite": {
27
+ "dataType": "tuple",
28
+ "coordinates": ["x", "y", "z"],
29
+ "values": [[1, 20, 1], [2, 21, 3]],
30
+ },
31
+ },
32
+ },
33
+ "ranges": {
34
+ "t": {
35
+ "type": "NdArray",
36
+ "dataType": "float",
37
+ "shape": [2],
38
+ "axisNames": ["t"],
39
+ "values": [264.9, 263.8],
40
+ },
41
+ },
42
+ },
43
+ ],
44
+ "referencing": [
45
+ {
46
+ "coordinates": ["x", "y", "z"],
47
+ "system": {
48
+ "type": "GeographicCRS",
49
+ "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
50
+ },
51
+ }
52
+ ],
53
+ "parameters": {
54
+ "t": {
55
+ "type": "Parameter",
56
+ "description": "Temperature",
57
+ "unit": {"symbol": "K"},
58
+ "observedProperty": {"id": "t", "label": {"en": "Temperature"}},
59
+ },
60
+ },
61
+ }
62
+
63
+ def test_circle_to_xarray_param_t_no_collision(self):
64
+ """to_xarray works with param 't' - no collision since dims use 'datetimes'."""
65
+ decoder = Circle.Circle(self.covjson)
66
+ ds = decoder.to_xarray()
67
+
68
+ # Param 't' should be in data_vars (no collision with 'datetimes' dim)
69
+ assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}"
70
+ # Time dimension is 'datetimes', not 't'
71
+ assert "datetimes" in ds.dims
@@ -314,3 +314,13 @@ class TestDecoder:
314
314
  # MARS metadata from the first coverage is attached as dataset attrs
315
315
  assert ds.attrs["step"] == "0"
316
316
  assert ds.attrs["class"] == "od"
317
+
318
+ def test_to_xarray_param_t_no_collision(self):
319
+ """to_xarray works with param 't' - no collision since dims use 'time'."""
320
+ decoder = Frame.Frame(self.covjson)
321
+ ds = decoder.to_xarray()
322
+
323
+ # Param 't' should be in data_vars (no collision with 'time' dim)
324
+ assert "t" in ds.data_vars, f"Expected 't' in data_vars, got {list(ds.data_vars)}"
325
+ # Time dimension is 'time', not 't'
326
+ assert "time" in ds.dims