covjsonkit 0.2.16__tar.gz → 0.2.18__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.16 → covjsonkit-0.2.18}/PKG-INFO +1 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/BoundingBox.py +2 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Circle.py +2 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Path.py +2 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/VerticalProfile.py +3 -2
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/BoundingBox.py +3 -3
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Circle.py +3 -3
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Frame.py +3 -3
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Grid.py +8 -5
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Path.py +2 -2
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Position.py +8 -5
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Shapefile.py +3 -3
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/VerticalProfile.py +2 -2
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/Wkt.py +3 -3
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/encoder.py +188 -3
- covjsonkit-0.2.18/covjsonkit/version.py +1 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit.egg-info/PKG-INFO +1 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit.egg-info/SOURCES.txt +1 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/pyproject.toml +1 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/conftest.py +1 -1
- covjsonkit-0.2.18/tests/geojsontest.py +16 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_bounding_box_from_polytope.py +1 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_position_from_polytope.py +2 -2
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_time_series_from_polytope.py +43 -4
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_vertical_profile_from_polytope.py +1 -1
- covjsonkit-0.2.16/covjsonkit/version.py +0 -1
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/.github/ci-hpc-config.yml +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/.github/workflows/ci.yaml +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/.github/workflows/label-public-pr.yml +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/.gitignore +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/.pre-commit-config.yaml +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/ACKNOWLEDGEMENTS.rst +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/LICENSE +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/MANIFEST.in +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/Makefile +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/README.md +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/Coverage.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/CoverageCollection.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/__init__.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/api.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/config.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/__init__.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/dwd/param.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/dwd/param_id.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/dwd/unit.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/ecmwf/param.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/ecmwf/param_id.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/data/ecmwf/unit.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Frame.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Grid.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Position.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Shapefile.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/TimeSeries.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/Wkt.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/__init__.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/decoder/decoder.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/TimeSeries.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/encoder/__init__.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/param_db.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit/utils.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit.egg-info/dependency_links.txt +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit.egg-info/not-zip-safe +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit.egg-info/requires.txt +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/covjsonkit.egg-info/top_level.txt +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/docs/images/ECMWF_logo.svg.png +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/docs/images/Logo_Destination_Earth_Colours.png +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/example_config.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/requirements.txt +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/scripts/generate_param_db.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/setup.cfg +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/setup.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_coverage1.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_coverage2.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_geotiff.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_multipoint_coverage.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_path_coverage.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_timeseries_coverage.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_timeseries_xyz_coverage.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_verticalprofile_coverage.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/data/test_verticalprofile_xyz_coverage.json +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/requirements_test.txt +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_bounding_box.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_frame.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_grid.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_shapefile.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_time_series.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_vertical_profile.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_decoder_wkt.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_bounding_box.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_circle_from_polytope.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_frame.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_frame_from_polytope.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_grid.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_grid_from_polytope.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_path.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_path_from_polytope.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_shapefile.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_shapefile_from_polytope.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_time_series.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_wkt.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_encoder_wkt_from_polytope.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_geojson.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_geotiff.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_util_merge.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_xarray_multipoint.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_xarray_path.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_xarray_ts.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tests/test_xarray_vp.py +0 -0
- {covjsonkit-0.2.16 → covjsonkit-0.2.18}/tox.ini +0 -0
|
@@ -8,6 +8,7 @@ except ImportError:
|
|
|
8
8
|
import xarray as xr
|
|
9
9
|
from scipy.spatial import cKDTree
|
|
10
10
|
|
|
11
|
+
from ..encoder.encoder import sort_step_values
|
|
11
12
|
from .decoder import Decoder
|
|
12
13
|
|
|
13
14
|
|
|
@@ -177,7 +178,7 @@ class BoundingBox(Decoder):
|
|
|
177
178
|
|
|
178
179
|
datetimes = sorted(list(set(datetimes)))
|
|
179
180
|
numbers = sorted(list(set(numbers)))
|
|
180
|
-
steps =
|
|
181
|
+
steps = sort_step_values(list(set(steps)))
|
|
181
182
|
|
|
182
183
|
new_values = {}
|
|
183
184
|
for parameter in values.keys():
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import xarray as xr
|
|
2
2
|
|
|
3
|
+
from ..encoder.encoder import sort_step_values
|
|
3
4
|
from .decoder import Decoder
|
|
4
5
|
|
|
5
6
|
|
|
@@ -119,7 +120,7 @@ class Circle(Decoder):
|
|
|
119
120
|
|
|
120
121
|
datetimes = sorted(list(set(datetimes)))
|
|
121
122
|
numbers = sorted(list(set(numbers)))
|
|
122
|
-
steps =
|
|
123
|
+
steps = sort_step_values(list(set(steps)))
|
|
123
124
|
|
|
124
125
|
new_values = {}
|
|
125
126
|
for parameter in values.keys():
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import xarray as xr
|
|
2
2
|
|
|
3
|
+
from ..encoder.encoder import sort_step_values
|
|
3
4
|
from .decoder import Decoder
|
|
4
5
|
|
|
5
6
|
|
|
@@ -117,7 +118,7 @@ class Path(Decoder):
|
|
|
117
118
|
|
|
118
119
|
datetimes = list(set(datetimes))
|
|
119
120
|
numbers = list(set(numbers))
|
|
120
|
-
steps = list(set(steps))
|
|
121
|
+
steps = sort_step_values(list(set(steps)))
|
|
121
122
|
|
|
122
123
|
new_values = {}
|
|
123
124
|
for parameter in values.keys():
|
|
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|
|
3
3
|
|
|
4
4
|
import xarray as xr
|
|
5
5
|
|
|
6
|
+
from ..encoder.encoder import parse_step_string, sort_step_values
|
|
6
7
|
from .decoder import Decoder
|
|
7
8
|
|
|
8
9
|
|
|
@@ -157,7 +158,7 @@ class VerticalProfile(Decoder):
|
|
|
157
158
|
|
|
158
159
|
nums = list(set(num))
|
|
159
160
|
datetime = list(set(datetime))
|
|
160
|
-
steps = list(set(steps))
|
|
161
|
+
steps = sort_step_values(list(set(steps)))
|
|
161
162
|
|
|
162
163
|
for parameter in self.parameters:
|
|
163
164
|
if len(param_values[parameter]) <= domain_idx:
|
|
@@ -176,7 +177,7 @@ class VerticalProfile(Decoder):
|
|
|
176
177
|
param_values[parameter][domain_idx][i][j].append([])
|
|
177
178
|
for coverage in self.covjson["coverages"]:
|
|
178
179
|
new_step = (
|
|
179
|
-
dt.fromisoformat(date.replace("Z", "")) + timedelta(hours=
|
|
180
|
+
dt.fromisoformat(date.replace("Z", "")) + timedelta(hours=parse_step_string(step))
|
|
180
181
|
).isoformat() + "Z"
|
|
181
182
|
if (
|
|
182
183
|
coverage["mars:metadata"]["number"] == num
|
|
@@ -3,7 +3,7 @@ import time
|
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
|
-
from .encoder import Encoder
|
|
6
|
+
from .encoder import Encoder, normalize_step_value
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class BoundingBox(Encoder):
|
|
@@ -107,7 +107,7 @@ class BoundingBox(Encoder):
|
|
|
107
107
|
dv_dict = {}
|
|
108
108
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
109
109
|
mars_metadata["number"] = int(num)
|
|
110
|
-
mars_metadata["step"] =
|
|
110
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
111
111
|
mars_metadata["Forecast date"] = str(datetime)
|
|
112
112
|
for dv in dataset.data_vars:
|
|
113
113
|
dv_dict[dv] = dataset[dv].sel(number=num, steps=step, datetimes=datetime).values.tolist()
|
|
@@ -196,7 +196,7 @@ class BoundingBox(Encoder):
|
|
|
196
196
|
for step in val_dict.keys():
|
|
197
197
|
mm = mars_metadata.copy()
|
|
198
198
|
mm["number"] = num
|
|
199
|
-
mm["step"] = step
|
|
199
|
+
mm["step"] = normalize_step_value(step)
|
|
200
200
|
mm["Forecast date"] = date
|
|
201
201
|
self.add_coverage(mm, coords[date], val_dict[step])
|
|
202
202
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from .encoder import Encoder
|
|
3
|
+
from .encoder import Encoder, normalize_step_value
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Circle(Encoder):
|
|
@@ -104,7 +104,7 @@ class Circle(Encoder):
|
|
|
104
104
|
dv_dict = {}
|
|
105
105
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
106
106
|
mars_metadata["number"] = int(num)
|
|
107
|
-
mars_metadata["step"] =
|
|
107
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
108
108
|
mars_metadata["Forecast date"] = str(datetime)
|
|
109
109
|
for dv in dataset.data_vars:
|
|
110
110
|
dv_dict[dv] = dataset[dv].sel(number=num, steps=step, datetimes=datetime).values.tolist()
|
|
@@ -194,7 +194,7 @@ class Circle(Encoder):
|
|
|
194
194
|
for step in val_dict.keys():
|
|
195
195
|
mm = mars_metadata.copy()
|
|
196
196
|
mm["number"] = num
|
|
197
|
-
mm["step"] = step
|
|
197
|
+
mm["step"] = normalize_step_value(step)
|
|
198
198
|
mm["Forecast date"] = date
|
|
199
199
|
self.add_coverage(mm, coords[date], val_dict[step])
|
|
200
200
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from .encoder import Encoder
|
|
3
|
+
from .encoder import Encoder, normalize_step_value
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Frame(Encoder):
|
|
@@ -104,7 +104,7 @@ class Frame(Encoder):
|
|
|
104
104
|
dv_dict = {}
|
|
105
105
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
106
106
|
mars_metadata["number"] = int(num)
|
|
107
|
-
mars_metadata["step"] =
|
|
107
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
108
108
|
mars_metadata["Forecast date"] = str(datetime)
|
|
109
109
|
for dv in dataset.data_vars:
|
|
110
110
|
dv_dict[dv] = dataset[dv].sel(number=num, steps=step, datetimes=datetime).values.tolist()
|
|
@@ -189,7 +189,7 @@ class Frame(Encoder):
|
|
|
189
189
|
for step in val_dict.keys():
|
|
190
190
|
mm = mars_metadata.copy()
|
|
191
191
|
mm["number"] = num
|
|
192
|
-
mm["step"] = step
|
|
192
|
+
mm["step"] = normalize_step_value(step)
|
|
193
193
|
mm["Forecast date"] = date
|
|
194
194
|
self.add_coverage(mm, coords[date], val_dict[step])
|
|
195
195
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
+
from datetime import timedelta
|
|
3
4
|
|
|
4
5
|
import pandas as pd
|
|
5
6
|
|
|
6
|
-
from .encoder import Encoder
|
|
7
|
+
from .encoder import Encoder, normalize_step_value
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Grid(Encoder):
|
|
@@ -105,7 +106,7 @@ class Grid(Encoder):
|
|
|
105
106
|
dv_dict = {}
|
|
106
107
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
107
108
|
mars_metadata["number"] = int(num)
|
|
108
|
-
mars_metadata["step"] =
|
|
109
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
109
110
|
mars_metadata["Forecast date"] = str(datetime)
|
|
110
111
|
for dv in dataset.data_vars:
|
|
111
112
|
nested_list = dataset[dv].sel(datetimes=datetime, number=num, steps=step).values.tolist()
|
|
@@ -181,11 +182,13 @@ class Grid(Encoder):
|
|
|
181
182
|
logging.debug("The range_dict created was: %s", range_dict) # noqa: E501
|
|
182
183
|
|
|
183
184
|
coordinates = {}
|
|
184
|
-
coordinates["t"] =
|
|
185
|
+
coordinates["t"] = [int(s.total_seconds() // 3600) if isinstance(s, timedelta) else s for s in fields["step"]]
|
|
185
186
|
|
|
186
187
|
for date in coords.keys():
|
|
187
188
|
coordinates[date] = {}
|
|
188
|
-
coordinates[date]["t"] =
|
|
189
|
+
coordinates[date]["t"] = [
|
|
190
|
+
int(s.total_seconds() // 3600) if isinstance(s, timedelta) else s for s in fields["step"]
|
|
191
|
+
]
|
|
189
192
|
coordinates[date]["levelist"] = list(fields["levels"])
|
|
190
193
|
coordinates[date]["latitude"] = []
|
|
191
194
|
coordinates[date]["longitude"] = []
|
|
@@ -211,7 +214,7 @@ class Grid(Encoder):
|
|
|
211
214
|
val_dict[para].extend(combined_dict[date][num][para][step])
|
|
212
215
|
mm = mars_metadata.copy()
|
|
213
216
|
mm["number"] = num
|
|
214
|
-
mm["step"] = step
|
|
217
|
+
mm["step"] = normalize_step_value(step)
|
|
215
218
|
mm["Forecast date"] = date
|
|
216
219
|
self.add_coverage(mm, coordinates[date], val_dict)
|
|
217
220
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from .encoder import Encoder
|
|
3
|
+
from .encoder import Encoder, normalize_step_value
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Path(Encoder):
|
|
@@ -104,7 +104,7 @@ class Path(Encoder):
|
|
|
104
104
|
dv_dict = {}
|
|
105
105
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
106
106
|
mars_metadata["number"] = int(num)
|
|
107
|
-
mars_metadata["step"] =
|
|
107
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
108
108
|
mars_metadata["Forecast date"] = str(datetime)
|
|
109
109
|
for dv in dataset.data_vars:
|
|
110
110
|
dv_dict[dv] = dataset[dv].sel(number=num, steps=step, datetimes=datetime).values.tolist()
|
|
@@ -196,11 +196,14 @@ class Position(Encoder):
|
|
|
196
196
|
new_date = pd.Timestamp(date).strftime(date_format)
|
|
197
197
|
start_time = datetime.strptime(new_date, date_format)
|
|
198
198
|
# add current date to list by converting it to iso format
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
if isinstance(step, timedelta):
|
|
200
|
+
stamp = start_time + step
|
|
201
|
+
else:
|
|
202
|
+
try:
|
|
203
|
+
int(step)
|
|
204
|
+
except ValueError:
|
|
205
|
+
step = step[0]
|
|
206
|
+
stamp = start_time + timedelta(hours=int(step))
|
|
204
207
|
coordinates[date][i]["t"].append(stamp.isoformat() + "Z")
|
|
205
208
|
break
|
|
206
209
|
break
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from .encoder import Encoder
|
|
3
|
+
from .encoder import Encoder, normalize_step_value
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Shapefile(Encoder):
|
|
@@ -104,7 +104,7 @@ class Shapefile(Encoder):
|
|
|
104
104
|
dv_dict = {}
|
|
105
105
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
106
106
|
mars_metadata["number"] = int(num)
|
|
107
|
-
mars_metadata["step"] =
|
|
107
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
108
108
|
mars_metadata["Forecast date"] = str(datetime)
|
|
109
109
|
for dv in dataset.data_vars:
|
|
110
110
|
dv_dict[dv] = dataset[dv].sel(number=num, steps=step, datetimes=datetime).values.tolist()
|
|
@@ -189,7 +189,7 @@ class Shapefile(Encoder):
|
|
|
189
189
|
for step in val_dict.keys():
|
|
190
190
|
mm = mars_metadata.copy()
|
|
191
191
|
mm["number"] = num
|
|
192
|
-
mm["step"] = step
|
|
192
|
+
mm["step"] = normalize_step_value(step)
|
|
193
193
|
mm["Forecast date"] = date
|
|
194
194
|
self.add_coverage(mm, coords[date], val_dict[step])
|
|
195
195
|
|
|
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
|
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
|
-
from .encoder import Encoder
|
|
7
|
+
from .encoder import Encoder, normalize_step_value
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class VerticalProfile(Encoder):
|
|
@@ -106,7 +106,7 @@ class VerticalProfile(Encoder):
|
|
|
106
106
|
for metadata in dataset.attrs:
|
|
107
107
|
mars_metadata[metadata] = dataset.attrs[metadata]
|
|
108
108
|
mars_metadata["number"] = int(num)
|
|
109
|
-
mars_metadata["step"] =
|
|
109
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
110
110
|
|
|
111
111
|
self.add_coverage(
|
|
112
112
|
mars_metadata,
|
|
@@ -4,7 +4,7 @@ import time
|
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
|
-
from .encoder import Encoder
|
|
7
|
+
from .encoder import Encoder, normalize_step_value
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Wkt(Encoder):
|
|
@@ -108,7 +108,7 @@ class Wkt(Encoder):
|
|
|
108
108
|
dv_dict = {}
|
|
109
109
|
mars_metadata = {metadata: dataset.attrs[metadata] for metadata in dataset.attrs}
|
|
110
110
|
mars_metadata["number"] = int(num)
|
|
111
|
-
mars_metadata["step"] =
|
|
111
|
+
mars_metadata["step"] = normalize_step_value(step)
|
|
112
112
|
mars_metadata["Forecast date"] = str(datetime)
|
|
113
113
|
for dv in dataset.data_vars:
|
|
114
114
|
dv_dict[dv] = dataset[dv].sel(number=num, steps=step, datetimes=datetime).values.tolist()
|
|
@@ -198,7 +198,7 @@ class Wkt(Encoder):
|
|
|
198
198
|
for step in val_dict.keys():
|
|
199
199
|
mm = mars_metadata.copy()
|
|
200
200
|
mm["number"] = num
|
|
201
|
-
mm["step"] = step
|
|
201
|
+
mm["step"] = normalize_step_value(step)
|
|
202
202
|
mm["Forecast date"] = date
|
|
203
203
|
self.add_coverage(mm, coords[date], val_dict[step])
|
|
204
204
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from datetime import timedelta
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
7
|
+
import numpy as np
|
|
6
8
|
import orjson
|
|
7
9
|
import pandas as pd
|
|
8
10
|
from covjson_pydantic.coverage import CoverageCollection
|
|
@@ -11,6 +13,170 @@ from covjson_pydantic.domain import DomainType
|
|
|
11
13
|
from covjsonkit.param_db import get_param_ids, get_params, get_units
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
def timedelta_to_step_string(td: timedelta) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Convert a timedelta object to a step string in the format 'XhYm'.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
td: timedelta object representing the step
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
String in format 'Xh', 'Ym', or 'XhYm' depending on the timedelta value
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
timedelta(hours=13) -> "13h"
|
|
28
|
+
timedelta(hours=13, minutes=30) -> "13h30m"
|
|
29
|
+
timedelta(minutes=30) -> "30m"
|
|
30
|
+
timedelta(hours=0, minutes=0) -> "0h"
|
|
31
|
+
"""
|
|
32
|
+
total_seconds = int(td.total_seconds())
|
|
33
|
+
hours = total_seconds // 3600
|
|
34
|
+
minutes = (total_seconds % 3600) // 60
|
|
35
|
+
|
|
36
|
+
if hours > 0 and minutes > 0:
|
|
37
|
+
return f"{hours}h{minutes}m"
|
|
38
|
+
elif hours > 0:
|
|
39
|
+
return f"{hours}h"
|
|
40
|
+
elif minutes > 0:
|
|
41
|
+
return f"{minutes}m"
|
|
42
|
+
else:
|
|
43
|
+
return "0h"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_step_string(step_str: str) -> float:
|
|
47
|
+
"""
|
|
48
|
+
Parse a step string in format 'XhYm' and return total hours as float.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
step_str: Step string in format 'Xh', 'Ym', or 'XhYm'
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Total hours as a float value
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
parse_step_string("13h") -> 13.0
|
|
58
|
+
parse_step_string("13h30m") -> 13.5
|
|
59
|
+
parse_step_string("30m") -> 0.5
|
|
60
|
+
parse_step_string("0h") -> 0.0
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(step_str, (int, float)):
|
|
63
|
+
return float(step_str)
|
|
64
|
+
|
|
65
|
+
step_str = str(step_str)
|
|
66
|
+
hours = 0.0
|
|
67
|
+
minutes = 0.0
|
|
68
|
+
|
|
69
|
+
# Parse hours
|
|
70
|
+
if "h" in step_str:
|
|
71
|
+
parts = step_str.split("h")
|
|
72
|
+
hours = float(parts[0])
|
|
73
|
+
step_str = parts[1] if len(parts) > 1 else ""
|
|
74
|
+
|
|
75
|
+
# Parse minutes
|
|
76
|
+
if "m" in step_str and step_str:
|
|
77
|
+
minutes = float(step_str.replace("m", ""))
|
|
78
|
+
|
|
79
|
+
return hours + (minutes / 60.0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def sort_step_values(steps: list) -> list:
|
|
83
|
+
"""
|
|
84
|
+
Sort a list of step values that might be in various formats.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
steps: List of step values (strings like "13h30m", integers, or floats)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Sorted list of step values in their original format
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
sort_step_values(["13h30m", "12h", "14h"]) -> ["12h", "13h30m", "14h"]
|
|
94
|
+
"""
|
|
95
|
+
# Create tuples of (original_value, numeric_value) for sorting
|
|
96
|
+
step_tuples = [(step, parse_step_string(step)) for step in steps]
|
|
97
|
+
# Sort by numeric value
|
|
98
|
+
step_tuples.sort(key=lambda x: x[1])
|
|
99
|
+
# Return original values in sorted order
|
|
100
|
+
return [step[0] for step in step_tuples]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def normalize_step_value(step):
|
|
104
|
+
"""
|
|
105
|
+
Normalize a step value from various formats, preserving integers when possible.
|
|
106
|
+
|
|
107
|
+
Handles:
|
|
108
|
+
- timedelta objects (datetime.timedelta) - converts to int if whole hours, else string "XhYm"
|
|
109
|
+
- numpy.timedelta64 objects - converts to int if whole hours, else string "XhYm"
|
|
110
|
+
- integers - returns as-is (legacy compatibility)
|
|
111
|
+
- floats - converts to int if whole hours, else string "XhYm"
|
|
112
|
+
- strings - returns as-is if already in correct format
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
step: Step value in various formats
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Integer for whole hour values, string in 'XhYm' format for sub-hourly values
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
normalize_step_value(timedelta(hours=13, minutes=30)) -> "13h30m"
|
|
122
|
+
normalize_step_value(timedelta(hours=13)) -> 13
|
|
123
|
+
normalize_step_value(13) -> 13
|
|
124
|
+
normalize_step_value(13.5) -> "13h30m"
|
|
125
|
+
normalize_step_value("13h30m") -> "13h30m"
|
|
126
|
+
"""
|
|
127
|
+
# If it's already a string, return as-is (assumes it's already in correct format)
|
|
128
|
+
if isinstance(step, str):
|
|
129
|
+
return step
|
|
130
|
+
|
|
131
|
+
# If it's a timedelta or pd.Timedelta, check if it has sub-hourly components
|
|
132
|
+
if isinstance(step, (timedelta, pd.Timedelta)):
|
|
133
|
+
if isinstance(step, pd.Timedelta):
|
|
134
|
+
td = step.to_pytimedelta()
|
|
135
|
+
else:
|
|
136
|
+
td = step
|
|
137
|
+
|
|
138
|
+
total_seconds = int(td.total_seconds())
|
|
139
|
+
hours = total_seconds // 3600
|
|
140
|
+
minutes = (total_seconds % 3600) // 60
|
|
141
|
+
|
|
142
|
+
# If there are minutes, return string format
|
|
143
|
+
if minutes > 0:
|
|
144
|
+
return timedelta_to_step_string(td)
|
|
145
|
+
# Otherwise return integer hours
|
|
146
|
+
return hours
|
|
147
|
+
|
|
148
|
+
# If it's a numpy timedelta64, convert to Python timedelta first
|
|
149
|
+
if isinstance(step, np.timedelta64):
|
|
150
|
+
td = pd.Timedelta(step).to_pytimedelta()
|
|
151
|
+
total_seconds = int(td.total_seconds())
|
|
152
|
+
hours = total_seconds // 3600
|
|
153
|
+
minutes = (total_seconds % 3600) // 60
|
|
154
|
+
|
|
155
|
+
if minutes > 0:
|
|
156
|
+
return timedelta_to_step_string(td)
|
|
157
|
+
return hours
|
|
158
|
+
|
|
159
|
+
# If it's an integer, return as-is (legacy compatibility)
|
|
160
|
+
if isinstance(step, (int, np.integer)):
|
|
161
|
+
return int(step)
|
|
162
|
+
|
|
163
|
+
# If it's a float, check if it has fractional hours
|
|
164
|
+
if isinstance(step, (float, np.floating)):
|
|
165
|
+
hours = int(step)
|
|
166
|
+
total_minutes = int(step * 60)
|
|
167
|
+
minutes = total_minutes % 60
|
|
168
|
+
|
|
169
|
+
if minutes > 0:
|
|
170
|
+
if hours > 0:
|
|
171
|
+
return f"{hours}h{minutes}m"
|
|
172
|
+
else:
|
|
173
|
+
return f"{minutes}m"
|
|
174
|
+
return hours
|
|
175
|
+
|
|
176
|
+
# Fallback: convert to string
|
|
177
|
+
return str(step)
|
|
178
|
+
|
|
179
|
+
|
|
14
180
|
class Encoder(ABC):
|
|
15
181
|
def __init__(self, type, domaintype):
|
|
16
182
|
"""
|
|
@@ -176,7 +342,15 @@ class Encoder(ABC):
|
|
|
176
342
|
def handle_non_leaf_node(child):
|
|
177
343
|
non_leaf_axes = ["latitude", "longitude", "param", date_key]
|
|
178
344
|
if child.axis.name not in non_leaf_axes:
|
|
179
|
-
|
|
345
|
+
val = child.values[0]
|
|
346
|
+
if isinstance(val, np.datetime64):
|
|
347
|
+
val = str(val)
|
|
348
|
+
elif isinstance(val, timedelta):
|
|
349
|
+
val = timedelta_to_step_string(val)
|
|
350
|
+
elif child.axis.name == "step":
|
|
351
|
+
# Step is not a timedelta! Need to normalize it
|
|
352
|
+
val = normalize_step_value(val)
|
|
353
|
+
mars_metadata[child.axis.name] = val
|
|
180
354
|
|
|
181
355
|
def handle_specific_axes(child):
|
|
182
356
|
if child.axis.name == "latitude":
|
|
@@ -272,7 +446,15 @@ class Encoder(ABC):
|
|
|
272
446
|
def handle_non_leaf_node_step(child):
|
|
273
447
|
non_leaf_axes = ["latitude", "longitude", "param", "date", "time"]
|
|
274
448
|
if child.axis.name not in non_leaf_axes:
|
|
275
|
-
|
|
449
|
+
val = child.values[0]
|
|
450
|
+
if isinstance(val, np.datetime64):
|
|
451
|
+
val = str(val)
|
|
452
|
+
elif isinstance(val, timedelta):
|
|
453
|
+
val = timedelta_to_step_string(val)
|
|
454
|
+
elif child.axis.name == "step":
|
|
455
|
+
# Step is not a timedelta! Need to normalize it
|
|
456
|
+
val = normalize_step_value(val)
|
|
457
|
+
mars_metadata[child.axis.name] = val
|
|
276
458
|
|
|
277
459
|
def handle_specific_axes_step(child):
|
|
278
460
|
if child.axis.name == "latitude":
|
|
@@ -441,7 +623,10 @@ class Encoder(ABC):
|
|
|
441
623
|
def handle_non_leaf_node_month(child):
|
|
442
624
|
non_leaf_axes = ["latitude", "longitude", "param", "year", "month"]
|
|
443
625
|
if child.axis.name not in non_leaf_axes:
|
|
444
|
-
|
|
626
|
+
val = child.values[0]
|
|
627
|
+
if isinstance(val, np.datetime64):
|
|
628
|
+
val = str(val)
|
|
629
|
+
mars_metadata[child.axis.name] = val
|
|
445
630
|
|
|
446
631
|
def handle_specific_axes_month(child):
|
|
447
632
|
if child.axis.name == "latitude":
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.18"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from covjsonkit.api import Covjsonkit
|
|
5
|
+
|
|
6
|
+
current_dir = os.path.dirname(__file__)
|
|
7
|
+
timeseries = os.path.join(current_dir, "data/test_timeseries_coverage.json")
|
|
8
|
+
with open(timeseries, "r") as f:
|
|
9
|
+
timeseries = json.load(f)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
cov = Covjsonkit().decode(timeseries)
|
|
13
|
+
ts = cov.to_geojson()
|
|
14
|
+
print(ts)
|
|
15
|
+
assert ts["type"] == "FeatureCollection"
|
|
16
|
+
assert len(ts["features"]) == 16
|
|
@@ -130,7 +130,7 @@ class TestPositionFromPolytopeReforecast:
|
|
|
130
130
|
|
|
131
131
|
shared_metadata = {
|
|
132
132
|
"class": "ce",
|
|
133
|
-
"date":
|
|
133
|
+
"date": "2024-03-01",
|
|
134
134
|
"Forecast date": "2025-07-14T06:00:00Z",
|
|
135
135
|
"domain": "g",
|
|
136
136
|
"expver": "4321",
|
|
@@ -168,7 +168,7 @@ class TestPositionFromPolytopeReforecast:
|
|
|
168
168
|
|
|
169
169
|
shared_metadata = {
|
|
170
170
|
"class": "ce",
|
|
171
|
-
"date":
|
|
171
|
+
"date": "2024-03-01",
|
|
172
172
|
"domain": "g",
|
|
173
173
|
"expver": "4321",
|
|
174
174
|
"levtype": "sfc",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
4
|
+
import orjson
|
|
5
|
+
import pytest
|
|
2
6
|
from conftest import chain, make_leaf, make_point, node, tip
|
|
3
7
|
from polytope_feature.datacube.tensor_index_tree import TensorIndexTree
|
|
4
8
|
|
|
@@ -29,7 +33,7 @@ def hdate_branch(hdate, lat, lon, result):
|
|
|
29
33
|
|
|
30
34
|
EXPECTED_HDATE_METADATA = {
|
|
31
35
|
"class": "ce",
|
|
32
|
-
"date":
|
|
36
|
+
"date": "2024-03-01",
|
|
33
37
|
"domain": "g",
|
|
34
38
|
"expver": "4321",
|
|
35
39
|
"levtype": "sfc",
|
|
@@ -199,12 +203,13 @@ class TestTimeseriesFromPolytope:
|
|
|
199
203
|
|
|
200
204
|
|
|
201
205
|
class TestTimeseriesFromPolytopeReforecast:
|
|
202
|
-
|
|
206
|
+
@pytest.mark.parametrize("date", [np.datetime64("2024-03-01"), np.datetime64("2024-03-01T00:00:00")])
|
|
207
|
+
def test_single_point(self, date):
|
|
203
208
|
# 1 hdate (with time pre-merged by polytope-mars), 1 point
|
|
204
209
|
tree = chain(
|
|
205
210
|
TensorIndexTree(),
|
|
206
211
|
node("class", ("ce",)),
|
|
207
|
-
node("date", (
|
|
212
|
+
node("date", (date,)),
|
|
208
213
|
hdate_branch(np.datetime64("2025-07-14T06:00:00"), 51.5, 6.5, [42.17]),
|
|
209
214
|
)
|
|
210
215
|
|
|
@@ -231,7 +236,11 @@ class TestTimeseriesFromPolytopeReforecast:
|
|
|
231
236
|
}
|
|
232
237
|
}
|
|
233
238
|
|
|
234
|
-
assert cov["mars:metadata"] == {
|
|
239
|
+
assert cov["mars:metadata"] == {
|
|
240
|
+
"Forecast date": "2025-07-14T06:00:00Z",
|
|
241
|
+
**EXPECTED_HDATE_METADATA,
|
|
242
|
+
"date": date.astype(str),
|
|
243
|
+
}
|
|
235
244
|
|
|
236
245
|
def test_multiple_times(self):
|
|
237
246
|
# 2 hdate values (pre-merged times from same day), 1 point → 2 coverages
|
|
@@ -453,3 +462,33 @@ class TestTimeseriesFromPolytopeReforecast:
|
|
|
453
462
|
assert cov["domain"]["axes"]["t"]["values"] == t
|
|
454
463
|
assert cov["ranges"]["dis06"]["values"] == vals
|
|
455
464
|
assert cov["mars:metadata"] == {"Forecast date": fc_date, **EXPECTED_HDATE_METADATA}
|
|
465
|
+
|
|
466
|
+
@pytest.mark.parametrize(
|
|
467
|
+
"json_module,date", [(json, np.datetime64("2024-03-01")), (orjson, np.datetime64("2024-03-01T00:00:00"))]
|
|
468
|
+
)
|
|
469
|
+
def test_reforecast_covjson_is_json_serialisable(self, json_module, date):
|
|
470
|
+
"""CoverageJSON produced by from_polytope_reforecast must be serialisable
|
|
471
|
+
with both stdlib json and orjson (i.e. no numpy.datetime64 left in the
|
|
472
|
+
mars:metadata dict — regression test for the date axis leak)."""
|
|
473
|
+
tree = chain(
|
|
474
|
+
TensorIndexTree(),
|
|
475
|
+
node("class", ("ce",)),
|
|
476
|
+
node("date", (date,)),
|
|
477
|
+
hdate_branch(np.datetime64("2025-07-14T06:00:00"), 51.5, 6.5, [42.17]),
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
covjson = Covjsonkit().encode("CoverageCollection", "PointSeries").from_polytope_reforecast(tree)
|
|
481
|
+
|
|
482
|
+
# stdlib json.dumps must not raise TypeError
|
|
483
|
+
serialised = json_module.dumps(covjson)
|
|
484
|
+
|
|
485
|
+
# The 'date' value in mars:metadata must be a plain string, not numpy.datetime64
|
|
486
|
+
for cov in covjson["coverages"]:
|
|
487
|
+
date_val = cov["mars:metadata"].get("date")
|
|
488
|
+
assert not isinstance(
|
|
489
|
+
date_val, np.datetime64
|
|
490
|
+
), f"mars:metadata['date'] is still a numpy.datetime64: {date_val!r}"
|
|
491
|
+
|
|
492
|
+
# Round-trip through the JSON module to ensure it can be deserialised back to a dict
|
|
493
|
+
deserialised = json_module.loads(serialised)
|
|
494
|
+
assert deserialised == covjson
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.16"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|