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.
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/PKG-INFO +1 -1
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Position.py +58 -58
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/TimeSeries.py +74 -65
- covjsonkit-0.2.19/covjsonkit/version.py +1 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/PKG-INFO +1 -1
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/SOURCES.txt +4 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/pyproject.toml +1 -1
- covjsonkit-0.2.19/tests/data/test_timeseries_param_t.json +47 -0
- covjsonkit-0.2.19/tests/data/test_verticalprofile_param_t.json +94 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_bounding_box.py +12 -6
- covjsonkit-0.2.19/tests/test_decoder_circle.py +71 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_frame.py +10 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_grid.py +23 -0
- covjsonkit-0.2.19/tests/test_decoder_position.py +25 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_shapefile.py +10 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_time_series.py +20 -2
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_vertical_profile.py +15 -6
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_decoder_wkt.py +10 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_multipoint.py +20 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_path.py +20 -0
- covjsonkit-0.2.18/covjsonkit/version.py +0 -1
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.github/ci-hpc-config.yml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.github/workflows/ci.yaml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.github/workflows/label-public-pr.yml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.gitignore +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/.pre-commit-config.yaml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/ACKNOWLEDGEMENTS.rst +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/LICENSE +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/MANIFEST.in +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/Makefile +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/README.md +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/Coverage.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/CoverageCollection.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/api.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/config.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/dwd/param.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/dwd/param_id.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/dwd/unit.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/ecmwf/param.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/ecmwf/param_id.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/data/ecmwf/unit.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/BoundingBox.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Circle.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Frame.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Grid.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Path.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Shapefile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/VerticalProfile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/Wkt.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/decoder/decoder.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/BoundingBox.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Circle.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Frame.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Grid.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Path.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Position.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Shapefile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/TimeSeries.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/VerticalProfile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/Wkt.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/encoder/encoder.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/param_db.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit/utils.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/dependency_links.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/not-zip-safe +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/requires.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/covjsonkit.egg-info/top_level.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/docs/images/ECMWF_logo.svg.png +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/docs/images/Logo_Destination_Earth_Colours.png +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/example_config.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/requirements.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/scripts/generate_param_db.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/setup.cfg +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/setup.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/conftest.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_coverage1.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_coverage2.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_geotiff.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_multipoint_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_path_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_timeseries_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_timeseries_xyz_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_verticalprofile_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/data/test_verticalprofile_xyz_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/geojsontest.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/requirements_test.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_bounding_box.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_bounding_box_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_circle_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_frame.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_frame_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_grid.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_grid_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_path.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_path_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_position_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_shapefile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_shapefile_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_time_series.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_time_series_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_vertical_profile_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_wkt.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_encoder_wkt_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_geojson.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_geotiff.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_util_merge.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_ts.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tests/test_xarray_vp.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.19}/tox.ini +0 -0
|
@@ -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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
"
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
name=f"{parameter}_domain_{domain_idx}",
|
|
178
|
+
dataarraydict[long_name] = (
|
|
179
|
+
dims,
|
|
180
|
+
[[[param_values]]],
|
|
181
|
+
attrs,
|
|
197
182
|
)
|
|
198
183
|
|
|
199
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
"
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
name=f"{parameter}_domain_{domain_idx}",
|
|
189
|
+
dataarraydict[long_name] = (
|
|
190
|
+
dims,
|
|
191
|
+
[[[param_values]]],
|
|
192
|
+
attrs,
|
|
205
193
|
)
|
|
206
194
|
|
|
207
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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"
|
|
@@ -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
|
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|