covjsonkit 0.2.18__tar.gz → 0.2.20__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.20}/PKG-INFO +1 -1
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Position.py +58 -58
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/TimeSeries.py +74 -65
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/encoder.py +70 -36
- covjsonkit-0.2.20/covjsonkit/version.py +1 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit.egg-info/PKG-INFO +1 -1
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit.egg-info/SOURCES.txt +4 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/pyproject.toml +1 -1
- covjsonkit-0.2.20/tests/data/test_timeseries_param_t.json +47 -0
- covjsonkit-0.2.20/tests/data/test_verticalprofile_param_t.json +94 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_bounding_box.py +12 -6
- covjsonkit-0.2.20/tests/test_decoder_circle.py +71 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_frame.py +10 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_grid.py +23 -0
- covjsonkit-0.2.20/tests/test_decoder_position.py +25 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_shapefile.py +10 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_time_series.py +20 -2
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_vertical_profile.py +15 -6
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_decoder_wkt.py +10 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_xarray_multipoint.py +20 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_xarray_path.py +20 -0
- covjsonkit-0.2.18/covjsonkit/version.py +0 -1
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/.github/ci-hpc-config.yml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/.github/workflows/ci.yaml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/.github/workflows/label-public-pr.yml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/.gitignore +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/.pre-commit-config.yaml +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/ACKNOWLEDGEMENTS.rst +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/LICENSE +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/MANIFEST.in +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/Makefile +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/README.md +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/Coverage.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/CoverageCollection.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/api.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/config.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/dwd/param.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/dwd/param_id.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/dwd/unit.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/ecmwf/param.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/ecmwf/param_id.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/data/ecmwf/unit.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/BoundingBox.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Circle.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Frame.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Grid.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Path.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Shapefile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/VerticalProfile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/Wkt.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/decoder/decoder.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/BoundingBox.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Circle.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Frame.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Grid.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Path.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Position.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Shapefile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/TimeSeries.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/VerticalProfile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/Wkt.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/encoder/__init__.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/param_db.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit/utils.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit.egg-info/dependency_links.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit.egg-info/not-zip-safe +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit.egg-info/requires.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/covjsonkit.egg-info/top_level.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/docs/images/ECMWF_logo.svg.png +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/docs/images/Logo_Destination_Earth_Colours.png +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/example_config.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/requirements.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/scripts/generate_param_db.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/setup.cfg +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/setup.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/conftest.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_coverage1.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_coverage2.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_geotiff.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_multipoint_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_path_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_timeseries_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_timeseries_xyz_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_verticalprofile_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/data/test_verticalprofile_xyz_coverage.json +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/geojsontest.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/requirements_test.txt +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_bounding_box.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_bounding_box_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_circle_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_frame.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_frame_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_grid.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_grid_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_path.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_path_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_position_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_shapefile.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_shapefile_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_time_series.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_time_series_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_vertical_profile_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_wkt.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_encoder_wkt_from_polytope.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_geojson.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_geotiff.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_util_merge.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_xarray_ts.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/tests/test_xarray_vp.py +0 -0
- {covjsonkit-0.2.18 → covjsonkit-0.2.20}/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),
|
|
@@ -589,7 +589,7 @@ class Encoder(ABC):
|
|
|
589
589
|
]
|
|
590
590
|
)
|
|
591
591
|
|
|
592
|
-
def walk_tree_month(self, tree, fields, coords, mars_metadata, range_dict):
|
|
592
|
+
def walk_tree_month(self, tree, fields, coords, mars_metadata, range_dict, _ctx=None):
|
|
593
593
|
"""Walk the result tree for monthly-mean streams (e.g. clmn).
|
|
594
594
|
|
|
595
595
|
These streams use ``year`` and ``month`` axes instead of ``date``/``time``/``step``.
|
|
@@ -597,28 +597,21 @@ class Encoder(ABC):
|
|
|
597
597
|
``"YYYY-MM"`` that plays the same role as ``date`` does in the standard
|
|
598
598
|
step-based tree walker.
|
|
599
599
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
once both are available — either when the second axis is encountered, or
|
|
604
|
-
at the leaf node.
|
|
600
|
+
Dates are accumulated in the order they are actually encountered during
|
|
601
|
+
tree traversal, so the ordering correctly reflects the tree structure
|
|
602
|
+
(e.g. month-major when the tree has month as the outer axis).
|
|
605
603
|
"""
|
|
604
|
+
if _ctx is None:
|
|
605
|
+
_ctx = {}
|
|
606
606
|
|
|
607
607
|
def _year_month_key(year, month):
|
|
608
608
|
return f"{int(year):04d}-{int(month):02d}"
|
|
609
609
|
|
|
610
|
-
def
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if not
|
|
614
|
-
|
|
615
|
-
for year in fields["years"]:
|
|
616
|
-
for month in fields["months"]:
|
|
617
|
-
key = _year_month_key(year, month)
|
|
618
|
-
if key not in fields["dates"]:
|
|
619
|
-
fields["dates"].append(key)
|
|
620
|
-
if key not in coords:
|
|
621
|
-
coords[key] = {"composite": [], "t": [key]}
|
|
610
|
+
def _register_date_key(key):
|
|
611
|
+
if key not in fields["dates"]:
|
|
612
|
+
fields["dates"].append(key)
|
|
613
|
+
if key not in coords:
|
|
614
|
+
coords[key] = {"composite": [], "t": [key]}
|
|
622
615
|
|
|
623
616
|
def handle_non_leaf_node_month(child):
|
|
624
617
|
non_leaf_axes = ["latitude", "longitude", "param", "year", "month"]
|
|
@@ -651,6 +644,10 @@ class Encoder(ABC):
|
|
|
651
644
|
for child in tree.children:
|
|
652
645
|
handle_non_leaf_node_month(child)
|
|
653
646
|
result = handle_specific_axes_month(child)
|
|
647
|
+
|
|
648
|
+
# Build a child context that inherits the current year/month
|
|
649
|
+
child_ctx = dict(_ctx)
|
|
650
|
+
|
|
654
651
|
if result is not None:
|
|
655
652
|
if child.axis.name == "latitude":
|
|
656
653
|
fields["lat"] = result
|
|
@@ -661,35 +658,72 @@ class Encoder(ABC):
|
|
|
661
658
|
elif child.axis.name == "param":
|
|
662
659
|
fields["param"] = result
|
|
663
660
|
elif child.axis.name == "year":
|
|
664
|
-
fields["years"] = result
|
|
665
|
-
|
|
661
|
+
fields["years"] = list(result) if fields.get("years") == [] else fields["years"]
|
|
662
|
+
child_ctx["years"] = result
|
|
663
|
+
child_ctx["_axis_order"] = _ctx.get("_axis_order", []) + ["year"]
|
|
664
|
+
# If month is already fixed in context, register dates now.
|
|
665
|
+
if "months" in _ctx:
|
|
666
|
+
for y in result:
|
|
667
|
+
for m in _ctx["months"]:
|
|
668
|
+
_register_date_key(_year_month_key(y, m))
|
|
666
669
|
elif child.axis.name == "month":
|
|
667
|
-
fields["months"] = result
|
|
668
|
-
|
|
670
|
+
fields["months"] = list(result) if fields.get("months") == [] else fields["months"]
|
|
671
|
+
child_ctx["months"] = result
|
|
672
|
+
# If year is already fixed in context, register dates now.
|
|
673
|
+
if "years" in _ctx:
|
|
674
|
+
for y in _ctx["years"]:
|
|
675
|
+
for m in result:
|
|
676
|
+
_register_date_key(_year_month_key(y, m))
|
|
677
|
+
# Track that month is the inner axis relative to year
|
|
678
|
+
child_ctx["_axis_order"] = _ctx.get("_axis_order", []) + ["month"]
|
|
669
679
|
elif child.axis.name == "number":
|
|
670
680
|
fields["number"] = result
|
|
671
681
|
|
|
672
|
-
self.walk_tree_month(child, fields, coords, mars_metadata, range_dict)
|
|
682
|
+
self.walk_tree_month(child, fields, coords, mars_metadata, range_dict, _ctx=child_ctx)
|
|
673
683
|
else:
|
|
674
|
-
# Leaf node —
|
|
675
|
-
|
|
684
|
+
# Leaf node — ensure all (year, month) combinations from context are registered.
|
|
685
|
+
ctx_years = _ctx.get("years", fields.get("years", []))
|
|
686
|
+
ctx_months = _ctx.get("months", fields.get("months", []))
|
|
687
|
+
for y in ctx_years:
|
|
688
|
+
for m in ctx_months:
|
|
689
|
+
_register_date_key(_year_month_key(y, m))
|
|
690
|
+
|
|
691
|
+
# Determine the dates in scope for this specific leaf. The loop order
|
|
692
|
+
# must match the actual tree axis order (outermost axis first) so that
|
|
693
|
+
# the flat result array is sliced correctly.
|
|
694
|
+
# _axis_order records axes in the order they were encountered top-down;
|
|
695
|
+
# the first entry is the outer axis at this leaf.
|
|
696
|
+
if ctx_years and ctx_months:
|
|
697
|
+
axis_order = _ctx.get("_axis_order", [])
|
|
698
|
+
# Default: if year was seen before month in the tree, year is outer.
|
|
699
|
+
year_is_outer = (
|
|
700
|
+
axis_order.index("year") < axis_order.index("month")
|
|
701
|
+
if ("year" in axis_order and "month" in axis_order)
|
|
702
|
+
else True
|
|
703
|
+
)
|
|
704
|
+
if year_is_outer:
|
|
705
|
+
leaf_dates = [_year_month_key(y, m) for y in ctx_years for m in ctx_months]
|
|
706
|
+
else:
|
|
707
|
+
leaf_dates = [_year_month_key(y, m) for m in ctx_months for y in ctx_years]
|
|
708
|
+
else:
|
|
709
|
+
leaf_dates = fields["dates"]
|
|
676
710
|
|
|
677
711
|
tree.values = [float(val) for val in tree.values]
|
|
678
712
|
if all(val is None for val in tree.result):
|
|
679
|
-
# Remove
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
713
|
+
# Remove date entries for this leaf that produced no data.
|
|
714
|
+
for key in leaf_dates:
|
|
715
|
+
if key in fields["dates"]:
|
|
716
|
+
fields["dates"].remove(key)
|
|
683
717
|
for level in fields["levels"]:
|
|
684
718
|
for num in fields["number"]:
|
|
685
719
|
for para in fields["param"]:
|
|
686
|
-
|
|
687
|
-
if
|
|
688
|
-
del range_dict[
|
|
720
|
+
rkey = (key, level, num, para)
|
|
721
|
+
if rkey in range_dict:
|
|
722
|
+
del range_dict[rkey]
|
|
689
723
|
else:
|
|
690
724
|
tree.result = [float(val) if val is not None else val for val in tree.result]
|
|
691
725
|
|
|
692
|
-
n_dates = len(
|
|
726
|
+
n_dates = len(leaf_dates)
|
|
693
727
|
n_levels = len(fields["levels"])
|
|
694
728
|
n_params = len(fields["param"])
|
|
695
729
|
|
|
@@ -698,11 +732,11 @@ class Encoder(ABC):
|
|
|
698
732
|
para_len = level_len / n_params if n_params else level_len
|
|
699
733
|
|
|
700
734
|
# Append this leaf's longitude values to composite coords for
|
|
701
|
-
# every date key
|
|
702
|
-
for date in
|
|
735
|
+
# every date key in scope.
|
|
736
|
+
for date in leaf_dates:
|
|
703
737
|
append_composite_coords_month(date, tree.values, fields["lat"])
|
|
704
738
|
|
|
705
|
-
for d, date in enumerate(
|
|
739
|
+
for d, date in enumerate(leaf_dates):
|
|
706
740
|
for l, level in enumerate(fields["levels"]): # noqa: E741
|
|
707
741
|
for i, num in enumerate(fields["number"]):
|
|
708
742
|
for j, para in enumerate(fields["param"]):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.20"
|
|
@@ -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
|
+
}
|