diff --git a/eccovjson/decoder/TimeSeries.py b/eccovjson/decoder/TimeSeries.py index 830ee7e..ebed33f 100644 --- a/eccovjson/decoder/TimeSeries.py +++ b/eccovjson/decoder/TimeSeries.py @@ -74,7 +74,7 @@ def to_xarray(self): try: t = [dt.datetime.strptime(coord[4], "%Y-%m-%d %H:%M:%S") for coord in coords_fc] except ValueError: - t = [dt.datetime.strptime(coord[4], "%Y-%m-%dT%H:%M:%S") for coord in coords_fc] + t = [dt.datetime.strptime(coord[4], "%Y-%m-%dT%H:%M:%SZ") for coord in coords_fc] param_coords = {"x": x, "y": y, "z": z, "number": num, "t": t} dataarray = xr.DataArray( diff --git a/eccovjson/encoder/BoundingBox.py b/eccovjson/encoder/BoundingBox.py index 366381e..0aecb42 100644 --- a/eccovjson/encoder/BoundingBox.py +++ b/eccovjson/encoder/BoundingBox.py @@ -1,4 +1,7 @@ +import json + import pandas as pd +from covjson_pydantic.coverage import Coverage from .encoder import Encoder @@ -17,7 +20,8 @@ def add_coverage(self, mars_metadata, coords, values): self.add_mars_metadata(new_coverage, mars_metadata) self.add_domain(new_coverage, coords) self.add_range(new_coverage, values) - self.covjson["coverages"].append(new_coverage) + cov = Coverage.model_validate_json(json.dumps(new_coverage)) + self.pydantic_coverage.coverages.append(cov) def add_domain(self, coverage, coords): coverage["domain"]["type"] = "Domain" @@ -26,7 +30,7 @@ def add_domain(self, coverage, coords): coverage["domain"]["axes"]["t"]["values"] = coords["t"] coverage["domain"]["axes"]["composite"] = {} coverage["domain"]["axes"]["composite"]["dataType"] = "tuple" - coverage["domain"]["axes"]["composite"]["coordinates"] = self.covjson["referencing"][0]["coordinates"] + coverage["domain"]["axes"]["composite"]["coordinates"] = self.pydantic_coverage.referencing[0].coordinates coverage["domain"]["axes"]["composite"]["values"] = coords["composite"] def add_range(self, coverage, values): @@ -120,7 +124,7 @@ def from_polytope(self, result): range_dict = {} coords = {} coords["composite"] = [] - coords["t"] = df["date"].unique()[0] + coords["t"] = [df["date"].unique()[0] + "Z"] for param in params: df_param = df[df["param"] == param] @@ -131,4 +135,4 @@ def from_polytope(self, result): coords["composite"].append([row[1]["latitude"], row[1]["longitude"]]) self.add_coverage(mars_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/Frame.py b/eccovjson/encoder/Frame.py index aaf4c81..0d01fed 100644 --- a/eccovjson/encoder/Frame.py +++ b/eccovjson/encoder/Frame.py @@ -1,4 +1,7 @@ +import json + import pandas as pd +from covjson_pydantic.coverage import Coverage from .encoder import Encoder @@ -17,7 +20,8 @@ def add_coverage(self, mars_metadata, coords, values): self.add_mars_metadata(new_coverage, mars_metadata) self.add_domain(new_coverage, coords) self.add_range(new_coverage, values) - self.covjson["coverages"].append(new_coverage) + cov = Coverage.model_validate_json(json.dumps(new_coverage)) + self.pydantic_coverage.coverages.append(cov) def add_domain(self, coverage, coords): coverage["domain"]["type"] = "Domain" @@ -26,7 +30,7 @@ def add_domain(self, coverage, coords): coverage["domain"]["axes"]["t"]["values"] = coords["t"] coverage["domain"]["axes"]["composite"] = {} coverage["domain"]["axes"]["composite"]["dataType"] = "tuple" - coverage["domain"]["axes"]["composite"]["coordinates"] = self.covjson["referencing"][0]["coordinates"] + coverage["domain"]["axes"]["composite"]["coordinates"] = self.pydantic_coverage.referencing[0].coordinates coverage["domain"]["axes"]["composite"]["values"] = coords["composite"] def add_range(self, coverage, values): @@ -120,7 +124,7 @@ def from_polytope(self, result): range_dict = {} coords = {} coords["composite"] = [] - coords["t"] = df["date"].unique()[0] + coords["t"] = [df["date"].unique()[0] + "Z"] for param in params: df_param = df[df["param"] == param] @@ -131,4 +135,4 @@ def from_polytope(self, result): coords["composite"].append([row[1]["latitude"], row[1]["longitude"]]) self.add_coverage(mars_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/Path.py b/eccovjson/encoder/Path.py index af7f153..3a0d9ee 100644 --- a/eccovjson/encoder/Path.py +++ b/eccovjson/encoder/Path.py @@ -1,4 +1,7 @@ +import json + import pandas as pd +from covjson_pydantic.coverage import Coverage from .encoder import Encoder @@ -17,14 +20,15 @@ def add_coverage(self, mars_metadata, coords, values): self.add_mars_metadata(new_coverage, mars_metadata) self.add_domain(new_coverage, coords) self.add_range(new_coverage, values) - self.covjson["coverages"].append(new_coverage) + cov = Coverage.model_validate_json(json.dumps(new_coverage)) + self.pydantic_coverage.coverages.append(cov) def add_domain(self, coverage, coords): coverage["domain"]["type"] = "Domain" coverage["domain"]["axes"] = {} coverage["domain"]["axes"]["composite"] = {} coverage["domain"]["axes"]["composite"]["dataType"] = "tuple" - coverage["domain"]["axes"]["composite"]["coordinates"] = self.covjson["referencing"][0]["coordinates"] + coverage["domain"]["axes"]["composite"]["coordinates"] = self.referencing coverage["domain"]["axes"]["composite"]["values"] = coords["composite"] def add_range(self, coverage, values): @@ -127,4 +131,4 @@ def from_polytope(self, result): coords["composite"].append([row[1]["date"], row[1]["latitude"], row[1]["longitude"]]) self.add_coverage(mars_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/Shapefile.py b/eccovjson/encoder/Shapefile.py index 45f42c9..f24bd3b 100644 --- a/eccovjson/encoder/Shapefile.py +++ b/eccovjson/encoder/Shapefile.py @@ -1,4 +1,7 @@ +import json + import pandas as pd +from covjson_pydantic.coverage import Coverage from .encoder import Encoder @@ -17,7 +20,8 @@ def add_coverage(self, mars_metadata, coords, values): self.add_mars_metadata(new_coverage, mars_metadata) self.add_domain(new_coverage, coords) self.add_range(new_coverage, values) - self.covjson["coverages"].append(new_coverage) + cov = Coverage.model_validate_json(json.dumps(new_coverage)) + self.pydantic_coverage.coverages.append(cov) def add_domain(self, coverage, coords): coverage["domain"]["type"] = "Domain" @@ -26,7 +30,7 @@ def add_domain(self, coverage, coords): coverage["domain"]["axes"]["t"]["values"] = coords["t"] coverage["domain"]["axes"]["composite"] = {} coverage["domain"]["axes"]["composite"]["dataType"] = "tuple" - coverage["domain"]["axes"]["composite"]["coordinates"] = self.covjson["referencing"][0]["coordinates"] + coverage["domain"]["axes"]["composite"]["coordinates"] = self.pydantic_coverage.referencing[0].coordinates coverage["domain"]["axes"]["composite"]["values"] = coords["composite"] def add_range(self, coverage, values): @@ -120,7 +124,7 @@ def from_polytope(self, result): range_dict = {} coords = {} coords["composite"] = [] - coords["t"] = df["date"].unique()[0] + coords["t"] = [df["date"].unique()[0] + "Z"] for param in params: df_param = df[df["param"] == param] @@ -131,4 +135,4 @@ def from_polytope(self, result): coords["composite"].append([row[1]["latitude"], row[1]["longitude"]]) self.add_coverage(mars_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/TimeSeries.py b/eccovjson/encoder/TimeSeries.py index 6c94c71..21f2a46 100644 --- a/eccovjson/encoder/TimeSeries.py +++ b/eccovjson/encoder/TimeSeries.py @@ -1,6 +1,8 @@ +import json from datetime import datetime, timedelta import pandas as pd +from covjson_pydantic.coverage import Coverage from .encoder import Encoder @@ -18,7 +20,8 @@ def add_coverage(self, mars_metadata, coords, values): self.add_mars_metadata(new_coverage, mars_metadata) self.add_domain(new_coverage, coords) self.add_range(new_coverage, values) - self.covjson["coverages"].append(new_coverage) + cov = Coverage.model_validate_json(json.dumps(new_coverage)) + self.pydantic_coverage.coverages.append(cov) def add_domain(self, coverage, coords): coverage["domain"]["type"] = "Domain" @@ -135,7 +138,7 @@ def from_polytope(self, result): for step in steps: # add current date to list by converting it to iso format stamp = start_time + timedelta(hours=int(step)) - coords["t"].append(stamp.isoformat()) + coords["t"].append(stamp.isoformat() + "Z") # increment start date by timedelta if "number" not in df.columns: @@ -156,4 +159,4 @@ def from_polytope(self, result): range_dict[param] = df_param["values"].values.tolist() self.add_coverage(new_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/VerticalProfile.py b/eccovjson/encoder/VerticalProfile.py index c497044..7251762 100644 --- a/eccovjson/encoder/VerticalProfile.py +++ b/eccovjson/encoder/VerticalProfile.py @@ -1,3 +1,5 @@ +import json + import pandas as pd from .encoder import Encoder @@ -148,4 +150,4 @@ def from_polytope(self, result): range_dict[param] = df_param["values"].values.tolist() self.add_coverage(new_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/Wkt.py b/eccovjson/encoder/Wkt.py index 782c1f5..d0e0de2 100644 --- a/eccovjson/encoder/Wkt.py +++ b/eccovjson/encoder/Wkt.py @@ -1,4 +1,7 @@ +import json + import pandas as pd +from covjson_pydantic.coverage import Coverage from .encoder import Encoder @@ -17,7 +20,8 @@ def add_coverage(self, mars_metadata, coords, values): self.add_mars_metadata(new_coverage, mars_metadata) self.add_domain(new_coverage, coords) self.add_range(new_coverage, values) - self.covjson["coverages"].append(new_coverage) + cov = Coverage.model_validate_json(json.dumps(new_coverage)) + self.pydantic_coverage.coverages.append(cov) def add_domain(self, coverage, coords): coverage["domain"]["type"] = "Domain" @@ -26,7 +30,7 @@ def add_domain(self, coverage, coords): coverage["domain"]["axes"]["t"]["values"] = coords["t"] coverage["domain"]["axes"]["composite"] = {} coverage["domain"]["axes"]["composite"]["dataType"] = "tuple" - coverage["domain"]["axes"]["composite"]["coordinates"] = self.covjson["referencing"][0]["coordinates"] + coverage["domain"]["axes"]["composite"]["coordinates"] = self.pydantic_coverage.referencing[0].coordinates coverage["domain"]["axes"]["composite"]["values"] = coords["composite"] def add_range(self, coverage, values): @@ -120,7 +124,7 @@ def from_polytope(self, result): range_dict = {} coords = {} coords["composite"] = [] - coords["t"] = df["date"].unique()[0] + coords["t"] = [df["date"].unique()[0] + "Z"] for param in params: df_param = df[df["param"] == param] @@ -131,4 +135,4 @@ def from_polytope(self, result): coords["composite"].append([row[1]["latitude"], row[1]["longitude"]]) self.add_coverage(mars_metadata, coords, range_dict) - return self.covjson + return json.loads(self.get_json()) diff --git a/eccovjson/encoder/encoder.py b/eccovjson/encoder/encoder.py index 5fc8941..cb939d5 100644 --- a/eccovjson/encoder/encoder.py +++ b/eccovjson/encoder/encoder.py @@ -1,7 +1,12 @@ +import json from abc import ABC, abstractmethod -from eccovjson.Coverage import Coverage -from eccovjson.CoverageCollection import CoverageCollection +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType +from covjson_pydantic.parameter import Parameter +from covjson_pydantic.reference_system import ReferenceSystemConnectionObject + +# from eccovjson.CoverageCollection import CoverageCollection from eccovjson.param_db import get_param_from_db, get_unit_from_db @@ -10,36 +15,69 @@ def __init__(self, type, domaintype): self.covjson = {} self.type = type + + self.referencing = [] + + domaintype = domaintype.lower() + + if domaintype == "pointseries": + self.domaintype = DomainType.point_series + elif domaintype == "multipoint": + self.domaintype = DomainType.multi_point + elif domaintype == "wkt": + self.domaintype = DomainType.multi_point + elif domaintype == "boundingbox": + self.domaintype = DomainType.multi_point + elif domaintype == "shapefile": + self.domaintype = DomainType.multi_point + elif domaintype == "frame": + self.domaintype = DomainType.multi_point + elif domaintype == "path": + self.domaintype = DomainType.trajectory + + self.pydantic_coverage = CoverageCollection( + type=type, coverages=[], domainType=self.domaintype, parameters={}, referencing=[] + ) + # self.covjson = self.pydantic_coverage.model_dump_json(exclude_none=True) self.parameters = [] - self.covjson["type"] = self.type - self.covjson["domainType"] = domaintype - self.covjson["coverages"] = [] - self.covjson["parameters"] = {} - self.covjson["referencing"] = [] - - if type == "Coverage": - self.coverage = Coverage(self.covjson) - elif type == "CoverageCollection": - self.coverage = CoverageCollection(self.covjson) - else: - raise TypeError("Type must be Coverage or CoverageCollection") + # self.covjson["type"] = self.type + # self.covjson["domainType"] = domaintype + # self.covjson["coverages"] = [] + # self.covjson["parameters"] = {} + # self.covjson["referencing"] = [] + + # if type == "Coverage": + # self.coverage = Coverage(self.covjson) + # elif type == "CoverageCollection": + # self.coverage = CoverageCollection(self.covjson) + # else: + # raise TypeError("Type must be Coverage or CoverageCollection") def add_parameter(self, param): param_dict = get_param_from_db(param) unit = get_unit_from_db(param_dict["unit_id"]) - self.covjson["parameters"][param_dict["shortname"]] = { + parameter = { "type": "Parameter", - "description": param_dict["description"], + "description": {"en": param_dict["description"]}, "unit": {"symbol": unit["name"]}, "observedProperty": { "id": param_dict["shortname"], "label": {"en": param_dict["name"]}, }, } + self.pydantic_coverage.parameters[param_dict["shortname"]] = Parameter.model_validate_json( + json.dumps(parameter) + ) self.parameters.append(param) def add_reference(self, reference): - self.covjson["referencing"].append(reference) + self.pydantic_coverage.referencing.append( + ReferenceSystemConnectionObject.model_validate_json(json.dumps(reference)) + ) + # self.pydantic_coverage.referencing.append(reference) + for ref in reference["coordinates"]: + if ref not in self.referencing: + self.referencing.append(ref) def convert_param_id_to_param(self, paramid): try: @@ -49,6 +87,10 @@ def convert_param_id_to_param(self, paramid): param_dict = get_param_from_db(int(param)) return param_dict["shortname"] + def get_json(self): + self.covjson = self.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + return self.covjson + @abstractmethod def add_coverage(self, mars_metadata, coords, values): pass diff --git a/requirements.txt b/requirements.txt index bf9c59a..7e93d6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ datetime polytope xarray==2022.12.0 pandas==1.5.2 +covjson-pydantic diff --git a/tests/test_encoder_bounding_box.py b/tests/test_encoder_bounding_box.py index a7b471a..74652f2 100644 --- a/tests/test_encoder_bounding_box.py +++ b/tests/test_encoder_bounding_box.py @@ -1,3 +1,6 @@ +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType + from eccovjson.api import Eccovjson @@ -141,75 +144,19 @@ def test_CoverageCollection(self): def test_standard_Coverage(self): encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": {}, - } + covjson = CoverageCollection( + type="CoverageCollection", coverages=[], domainType=DomainType.multi_point, parameters={}, referencing=[] + ) - assert encoder_obj.covjson == covjson + assert encoder_obj.get_json() == covjson.model_dump_json(exclude_none=True, indent=4) def test_add_parameter(self): encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") encoder_obj.add_parameter(167) encoder_obj.add_parameter(166) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": { - "10v": { - "type": "Parameter", - "description": ( - "This parameter is the northward component of the 10m wind." - " It is the horizontal speed of air moving towards the north" - ", at a height of ten metres" - " above the surface of the Earth, in metres per second." - "

Care should be taken when" - " comparing this parameter with observations, because" - " wind observations vary on small space" - " and time scales and are affected by the local terrain," - " vegetation and buildings that are" - " represented only on average in the ECMWF Integrated Forecasting" - " System.

This parameter" - " can be combined with the U component of 10m wind to give " - "the speed and direction of the horizontal" - " 10m wind." - ), - "unit": {"symbol": "m s**-1"}, - "observedProperty": {"id": "10v", "label": {"en": "10 metre V wind component"}}, - }, - "2t": { - "type": "Parameter", - "description": ( - "This parameter is the temperature of air at 2m above the surface of land," - " sea or in-land waters.

2m temperature is calculated by " - "interpolating between the lowest" - " model level and the Earth's surface, taking account of the " - "atmospheric conditions." - "" - " See further information .

This parameter has " - "units of kelvin (K). Temperature measured in kelvin" - " can be converted to degrees Celsius (°C) by subtracting 273.15." - "

Please note that the encodings listed" - " here for s2s & uerra (which includes encodings for carra/cerra) " - "include entries for Mean 2 metre temperature." - " The specific encoding for Mean 2 metre temperature can be found in 228004." - ), - "unit": {"symbol": "K"}, - "observedProperty": { - "id": "2t", - "label": {"en": "2 metre temperature"}, - }, - }, - }, - } - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_reference(self): encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") @@ -222,23 +169,10 @@ def test_add_reference(self): }, } ) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [ - { - "coordinates": ["x", "y", "z"], - "system": { - "type": "GeographicCRS", - "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - }, - } - ], - "parameters": {}, - } + encoder_obj.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_coverage(self): encoder = Eccovjson().encode("CoverageCollection", "BoundingBox") @@ -266,13 +200,14 @@ def test_add_coverage(self): "number": 0, } coords = {} - coords["t"] = ["2017-01-01T00:00:00"] + coords["t"] = ["2017-01-01T00:00:00Z"] coords["composite"] = [[1, 20, 1], [2, 21, 3], [3, 17, 7]] value = {"2t": [111, 222, 333]} encoder.add_coverage(metadata, coords, value) - # print(encoder.covjson) - print(encoder.covjson) + json_string = encoder.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + print(json_string) """ diff --git a/tests/test_encoder_frame.py b/tests/test_encoder_frame.py index 910db41..30a9fc7 100644 --- a/tests/test_encoder_frame.py +++ b/tests/test_encoder_frame.py @@ -1,3 +1,6 @@ +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType + from eccovjson.api import Eccovjson @@ -136,83 +139,27 @@ def setup_method(self, method): } def test_CoverageCollection(self): - encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") + encoder_obj = Eccovjson().encode("CoverageCollection", "Frame") assert encoder_obj.type == "CoverageCollection" def test_standard_Coverage(self): - encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": {}, - } + encoder_obj = Eccovjson().encode("CoverageCollection", "Frame") + covjson = CoverageCollection( + type="CoverageCollection", coverages=[], domainType=DomainType.multi_point, parameters={}, referencing=[] + ) - assert encoder_obj.covjson == covjson + assert encoder_obj.get_json() == covjson.model_dump_json(exclude_none=True, indent=4) def test_add_parameter(self): - encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") + encoder_obj = Eccovjson().encode("CoverageCollection", "Frame") encoder_obj.add_parameter(167) encoder_obj.add_parameter(166) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": { - "10v": { - "type": "Parameter", - "description": ( - "This parameter is the northward component of the 10m wind." - " It is the horizontal speed of air moving towards the north" - ", at a height of ten metres" - " above the surface of the Earth, in metres per second." - "

Care should be taken when" - " comparing this parameter with observations, because" - " wind observations vary on small space" - " and time scales and are affected by the local terrain," - " vegetation and buildings that are" - " represented only on average in the ECMWF Integrated Forecasting" - " System.

This parameter" - " can be combined with the U component of 10m wind to give " - "the speed and direction of the horizontal" - " 10m wind." - ), - "unit": {"symbol": "m s**-1"}, - "observedProperty": {"id": "10v", "label": {"en": "10 metre V wind component"}}, - }, - "2t": { - "type": "Parameter", - "description": ( - "This parameter is the temperature of air at 2m above the surface of land," - " sea or in-land waters.

2m temperature is calculated by " - "interpolating between the lowest" - " model level and the Earth's surface, taking account of the " - "atmospheric conditions." - "" - " See further information .

This parameter has " - "units of kelvin (K). Temperature measured in kelvin" - " can be converted to degrees Celsius (°C) by subtracting 273.15." - "

Please note that the encodings listed" - " here for s2s & uerra (which includes encodings for carra/cerra) " - "include entries for Mean 2 metre temperature." - " The specific encoding for Mean 2 metre temperature can be found in 228004." - ), - "unit": {"symbol": "K"}, - "observedProperty": { - "id": "2t", - "label": {"en": "2 metre temperature"}, - }, - }, - }, - } - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_reference(self): - encoder_obj = Eccovjson().encode("CoverageCollection", "BoundingBox") + encoder_obj = Eccovjson().encode("CoverageCollection", "Frame") encoder_obj.add_reference( { "coordinates": ["x", "y", "z"], @@ -222,26 +169,13 @@ def test_add_reference(self): }, } ) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [ - { - "coordinates": ["x", "y", "z"], - "system": { - "type": "GeographicCRS", - "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - }, - } - ], - "parameters": {}, - } + encoder_obj.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_coverage(self): - encoder = Eccovjson().encode("CoverageCollection", "BoundingBox") + encoder = Eccovjson().encode("CoverageCollection", "Frame") encoder.add_parameter(167) encoder.add_reference( { @@ -266,13 +200,14 @@ def test_add_coverage(self): "number": 0, } coords = {} - coords["t"] = ["2017-01-01T00:00:00"] + coords["t"] = ["2017-01-01T00:00:00Z"] coords["composite"] = [[1, 20, 1], [2, 21, 3], [3, 17, 7]] value = {"2t": [111, 222, 333]} encoder.add_coverage(metadata, coords, value) - # print(encoder.covjson) - print(encoder.covjson) + json_string = encoder.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + print(json_string) """ diff --git a/tests/test_encoder_path.py b/tests/test_encoder_path.py new file mode 100644 index 0000000..e2e21cf --- /dev/null +++ b/tests/test_encoder_path.py @@ -0,0 +1,229 @@ +import pytest +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType + +from eccovjson.api import Eccovjson + + +def get_timestamps(start_dt, end_dt, delta): + dates = [] + while start_dt <= end_dt: + # add current date to list by converting it to iso format + dates.append(start_dt.isoformat().replace("T", " ")) + # increment start date by timedelta + start_dt += delta + return dates + + +class TestEncoder: + def setup_method(self, method): + self.covjson = { + "type": "CoverageCollection", + "domainType": "MultiPoint", + "coverages": [ + { + "mars:metadata": { + "class": "od", + "stream": "oper", + "levtype": "pl", + "date": "20170101", + "step": "0", + "number": "0", + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "t": {"values": ["2017-01-01T00:00:00"]}, + "composite": { + "dataType": "tuple", + "coordinates": ["x", "y", "z"], + "values": [[1, 20, 1], [2, 21, 3], [3, 17, 7]], + }, + }, + }, + "ranges": { + "t": { + "type": "NdArray", + "dataType": "float", + "shape": [3], + "axisNames": ["t"], + "values": [ + 264.93115234375, + 263.83115234375, + 265.12313132266, + ], + }, + "p": { + "type": "NdArray", + "dataType": "float", + "shape": [3], + "axisNames": ["t"], + "values": [ + 9.93115234375, + 7.83115234375, + 14.12313132266, + ], + }, + }, + }, + { + "mars:metadata": { + "class": "od", + "stream": "oper", + "levtype": "pl", + "date": "20170101", + "step": "1", + "number": "0", + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "t": {"values": ["2017-01-01T01:00:00"]}, + "composite": { + "dataType": "tuple", + "coordinates": ["x", "y", "z"], + "values": [[1, 20, 1], [2, 21, 3], [3, 17, 7]], + }, + }, + }, + "ranges": { + "t": { + "type": "NdArray", + "dataType": "float", + "shape": [3], + "axisNames": ["t"], + "values": [ + 266.93115234375, + 293.83115234375, + 165.12313132266, + ], + }, + "p": { + "type": "NdArray", + "dataType": "float", + "shape": [3], + "axisNames": ["t"], + "values": [ + 1.93115234375, + 22.83115234375, + 12.12313132266, + ], + }, + }, + }, + ], + "referencing": [ + { + "coordinates": ["x", "y", "z"], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + }, + } + ], + "parameters": { + "2t": { + "type": "Parameter", + "description": "Temperature", + "unit": {"symbol": "K"}, + "observedProperty": {"id": "2t", "label": {"en": "Temperature"}}, + }, + "p": { + "type": "Parameter", + "description": "Pressure", + "unit": {"symbol": "pa"}, + "observedProperty": {"id": "p", "label": {"en": "Pressure"}}, + }, + }, + } + + @pytest.mark.skip(reason="Trajecotry not implemented in covjson-pydantic yet") + def test_CoverageCollection(self): + encoder_obj = Eccovjson().encode("CoverageCollection", "Path") + assert encoder_obj.type == "CoverageCollection" + + @pytest.mark.skip(reason="Trajecotry not implemented in covjson-pydantic yet") + def test_standard_Coverage(self): + encoder_obj = Eccovjson().encode("CoverageCollection", "Path") + covjson = CoverageCollection( + type="CoverageCollection", coverages=[], domainType=DomainType.trajectory, parameters={}, referencing=[] + ) + + assert encoder_obj.get_json() == covjson.model_dump_json(exclude_none=True, indent=4) + + @pytest.mark.skip(reason="Trajecotry not implemented in covjson-pydantic yet") + def test_add_parameter(self): + encoder_obj = Eccovjson().encode("CoverageCollection", "Path") + encoder_obj.add_parameter(167) + encoder_obj.add_parameter(166) + + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + + @pytest.mark.skip(reason="Trajecotry not implemented in covjson-pydantic yet") + def test_add_reference(self): + encoder_obj = Eccovjson().encode("CoverageCollection", "Path") + encoder_obj.add_reference( + { + "coordinates": ["x", "y", "z"], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + }, + } + ) + encoder_obj.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) + + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + + @pytest.mark.skip(reason="Trajecotry not implemented in covjson-pydantic yet") + def test_add_coverage(self): + encoder = Eccovjson().encode("CoverageCollection", "Path") + encoder.add_parameter(167) + encoder.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) + encoder.add_reference( + { + "coordinates": ["x", "y"], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + }, + } + ) + + # metadatas = [] + coords = [] + # values = [] + + metadata = { + "class": "od", + "stream": "oper", + "levtype": "pl", + "date": "20170101", + "step": "0", + "number": 0, + } + coords = {} + coords["composite"] = [ + ["2017-01-01T00:00:00Z", 20, 1], + ["2017-01-01T00:00:00Z", 21, 3], + ["2017-01-01T00:00:00Z", 17, 7], + ] + value = {"2t": [111, 222, 333]} + encoder.add_coverage(metadata, coords, value) + + json_string = encoder.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + print(json_string) + + """ + + def test_from_xarray(self): + ds = xr.open_dataset("new_timeseries.nc") + encoder = Eccovjson().encode("CoverageCollection", "PointSeries") + encoder.from_xarray(ds) + + """ diff --git a/tests/test_encoder_shapefile.py b/tests/test_encoder_shapefile.py index 1930133..5ad43b5 100644 --- a/tests/test_encoder_shapefile.py +++ b/tests/test_encoder_shapefile.py @@ -1,3 +1,6 @@ +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType + from eccovjson.api import Eccovjson @@ -141,75 +144,19 @@ def test_CoverageCollection(self): def test_standard_Coverage(self): encoder_obj = Eccovjson().encode("CoverageCollection", "shapefile") - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": {}, - } + covjson = CoverageCollection( + type="CoverageCollection", coverages=[], domainType=DomainType.multi_point, parameters={}, referencing=[] + ) - assert encoder_obj.covjson == covjson + assert encoder_obj.get_json() == covjson.model_dump_json(exclude_none=True, indent=4) def test_add_parameter(self): encoder_obj = Eccovjson().encode("CoverageCollection", "shapefile") encoder_obj.add_parameter(167) encoder_obj.add_parameter(166) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": { - "10v": { - "type": "Parameter", - "description": ( - "This parameter is the northward component of the 10m wind." - " It is the horizontal speed of air moving towards the north" - ", at a height of ten metres" - " above the surface of the Earth, in metres per second." - "

Care should be taken when" - " comparing this parameter with observations, because" - " wind observations vary on small space" - " and time scales and are affected by the local terrain," - " vegetation and buildings that are" - " represented only on average in the ECMWF Integrated Forecasting" - " System.

This parameter" - " can be combined with the U component of 10m wind to give " - "the speed and direction of the horizontal" - " 10m wind." - ), - "unit": {"symbol": "m s**-1"}, - "observedProperty": {"id": "10v", "label": {"en": "10 metre V wind component"}}, - }, - "2t": { - "type": "Parameter", - "description": ( - "This parameter is the temperature of air at 2m above the surface of land," - " sea or in-land waters.

2m temperature is calculated by " - "interpolating between the lowest" - " model level and the Earth's surface, taking account of the " - "atmospheric conditions." - "" - " See further information .

This parameter has " - "units of kelvin (K). Temperature measured in kelvin" - " can be converted to degrees Celsius (°C) by subtracting 273.15." - "

Please note that the encodings listed" - " here for s2s & uerra (which includes encodings for carra/cerra) " - "include entries for Mean 2 metre temperature." - " The specific encoding for Mean 2 metre temperature can be found in 228004." - ), - "unit": {"symbol": "K"}, - "observedProperty": { - "id": "2t", - "label": {"en": "2 metre temperature"}, - }, - }, - }, - } - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_reference(self): encoder_obj = Eccovjson().encode("CoverageCollection", "shapefile") @@ -222,23 +169,10 @@ def test_add_reference(self): }, } ) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [ - { - "coordinates": ["x", "y", "z"], - "system": { - "type": "GeographicCRS", - "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - }, - } - ], - "parameters": {}, - } + encoder_obj.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_coverage(self): encoder = Eccovjson().encode("CoverageCollection", "shapefile") @@ -266,13 +200,14 @@ def test_add_coverage(self): "number": 0, } coords = {} - coords["t"] = ["2017-01-01T00:00:00"] + coords["t"] = ["2017-01-01T00:00:00Z"] coords["composite"] = [[1, 20, 1], [2, 21, 3], [3, 17, 7]] value = {"2t": [111, 222, 333]} encoder.add_coverage(metadata, coords, value) - # print(encoder.covjson) - print(encoder.covjson) + json_string = encoder.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + print(json_string) """ diff --git a/tests/test_encoder_time_series.py b/tests/test_encoder_time_series.py index 88c3799..0ab4312 100644 --- a/tests/test_encoder_time_series.py +++ b/tests/test_encoder_time_series.py @@ -1,6 +1,9 @@ import random from datetime import datetime, timedelta +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType + from eccovjson.api import Eccovjson @@ -8,7 +11,7 @@ def get_timestamps(start_dt, end_dt, delta): dates = [] while start_dt <= end_dt: # add current date to list by converting it to iso format - dates.append(start_dt.isoformat().replace("T", " ")) + dates.append(start_dt.isoformat() + "Z") # .replace("T", " ")) # increment start date by timedelta start_dt += delta return dates @@ -152,75 +155,19 @@ def test_CoverageCollection(self): def test_standard_Coverage(self): encoder_obj = Eccovjson().encode("CoverageCollection", "PointSeries") - covjson = { - "type": "CoverageCollection", - "domainType": "PointSeries", - "coverages": [], - "referencing": [], - "parameters": {}, - } + covjson = CoverageCollection( + type="CoverageCollection", coverages=[], domainType=DomainType.point_series, parameters={}, referencing=[] + ) - assert encoder_obj.covjson == covjson + assert encoder_obj.get_json() == covjson.model_dump_json(exclude_none=True, indent=4) def test_add_parameter(self): encoder_obj = Eccovjson().encode("CoverageCollection", "PointSeries") encoder_obj.add_parameter(167) encoder_obj.add_parameter(166) - covjson = { - "type": "CoverageCollection", - "domainType": "PointSeries", - "coverages": [], - "referencing": [], - "parameters": { - "10v": { - "type": "Parameter", - "description": ( - "This parameter is the northward component of the 10m wind." - " It is the horizontal speed of air moving towards the north" - ", at a height of ten metres" - " above the surface of the Earth, in metres per second." - "

Care should be taken when" - " comparing this parameter with observations, because" - " wind observations vary on small space" - " and time scales and are affected by the local terrain," - " vegetation and buildings that are" - " represented only on average in the ECMWF Integrated Forecasting" - " System.

This parameter" - " can be combined with the U component of 10m wind to give " - "the speed and direction of the horizontal" - " 10m wind." - ), - "unit": {"symbol": "m s**-1"}, - "observedProperty": {"id": "10v", "label": {"en": "10 metre V wind component"}}, - }, - "2t": { - "type": "Parameter", - "description": ( - "This parameter is the temperature of air at 2m above the surface of land," - " sea or in-land waters.

2m temperature is calculated by " - "interpolating between the lowest" - " model level and the Earth's surface, taking account of the " - "atmospheric conditions." - "" - " See further information .

This parameter has " - "units of kelvin (K). Temperature measured in kelvin" - " can be converted to degrees Celsius (°C) by subtracting 273.15." - "

Please note that the encodings listed" - " here for s2s & uerra (which includes encodings for carra/cerra) " - "include entries for Mean 2 metre temperature." - " The specific encoding for Mean 2 metre temperature can be found in 228004." - ), - "unit": {"symbol": "K"}, - "observedProperty": { - "id": "2t", - "label": {"en": "2 metre temperature"}, - }, - }, - }, - } - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_reference(self): encoder_obj = Eccovjson().encode("CoverageCollection", "PointSeries") @@ -233,23 +180,10 @@ def test_add_reference(self): }, } ) - covjson = { - "type": "CoverageCollection", - "domainType": "PointSeries", - "coverages": [], - "referencing": [ - { - "coordinates": ["x", "y", "z"], - "system": { - "type": "GeographicCRS", - "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - }, - } - ], - "parameters": {}, - } + encoder_obj.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_coverage(self): encoder = Eccovjson().encode("CoverageCollection", "PointSeries") @@ -263,11 +197,12 @@ def test_add_coverage(self): }, } ) + encoder.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) # metadatas = [] coords = [] values = [] - for number in range(0, 50): + for number in range(0, 10): metadata = { "class": "od", "stream": "oper", @@ -278,7 +213,7 @@ def test_add_coverage(self): } timestamps = get_timestamps( datetime(2017, 1, 1, 0, 00), - datetime(2017, 1, 14, 0, 00), + datetime(2017, 1, 5, 0, 00), timedelta(hours=6), ) coord = { @@ -291,7 +226,11 @@ def test_add_coverage(self): value = {"2t": [random.uniform(230, 270) for _ in range(0, len(timestamps))]} values.append(value) encoder.add_coverage(metadata, coord, value) - # print(encoder.covjson) + + json_string = encoder.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + + print(json_string) # @pytest.mark.data # def test_from_xarray(self): diff --git a/tests/test_encoder_wkt.py b/tests/test_encoder_wkt.py index 70ec091..6af0bf4 100644 --- a/tests/test_encoder_wkt.py +++ b/tests/test_encoder_wkt.py @@ -1,3 +1,6 @@ +from covjson_pydantic.coverage import CoverageCollection +from covjson_pydantic.domain import DomainType + from eccovjson.api import Eccovjson @@ -5,7 +8,7 @@ def get_timestamps(start_dt, end_dt, delta): dates = [] while start_dt <= end_dt: # add current date to list by converting it to iso format - dates.append(start_dt.isoformat().replace("T", " ")) + dates.append(start_dt.isoformat() + "Z") # .replace("T", " ")) # increment start date by timedelta start_dt += delta return dates @@ -141,75 +144,19 @@ def test_CoverageCollection(self): def test_standard_Coverage(self): encoder_obj = Eccovjson().encode("CoverageCollection", "wkt") - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": {}, - } + covjson = CoverageCollection( + type="CoverageCollection", coverages=[], domainType=DomainType.multi_point, parameters={}, referencing=[] + ) - assert encoder_obj.covjson == covjson + assert encoder_obj.get_json() == covjson.model_dump_json(exclude_none=True, indent=4) def test_add_parameter(self): encoder_obj = Eccovjson().encode("CoverageCollection", "wkt") encoder_obj.add_parameter(167) encoder_obj.add_parameter(166) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [], - "parameters": { - "10v": { - "type": "Parameter", - "description": ( - "This parameter is the northward component of the 10m wind." - " It is the horizontal speed of air moving towards the north" - ", at a height of ten metres" - " above the surface of the Earth, in metres per second." - "

Care should be taken when" - " comparing this parameter with observations, because" - " wind observations vary on small space" - " and time scales and are affected by the local terrain," - " vegetation and buildings that are" - " represented only on average in the ECMWF Integrated Forecasting" - " System.

This parameter" - " can be combined with the U component of 10m wind to give " - "the speed and direction of the horizontal" - " 10m wind." - ), - "unit": {"symbol": "m s**-1"}, - "observedProperty": {"id": "10v", "label": {"en": "10 metre V wind component"}}, - }, - "2t": { - "type": "Parameter", - "description": ( - "This parameter is the temperature of air at 2m above the surface of land," - " sea or in-land waters.

2m temperature is calculated by " - "interpolating between the lowest" - " model level and the Earth's surface, taking account of the " - "atmospheric conditions." - "" - " See further information .

This parameter has " - "units of kelvin (K). Temperature measured in kelvin" - " can be converted to degrees Celsius (°C) by subtracting 273.15." - "

Please note that the encodings listed" - " here for s2s & uerra (which includes encodings for carra/cerra) " - "include entries for Mean 2 metre temperature." - " The specific encoding for Mean 2 metre temperature can be found in 228004." - ), - "unit": {"symbol": "K"}, - "observedProperty": { - "id": "2t", - "label": {"en": "2 metre temperature"}, - }, - }, - }, - } - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_reference(self): encoder_obj = Eccovjson().encode("CoverageCollection", "wkt") @@ -222,23 +169,10 @@ def test_add_reference(self): }, } ) - covjson = { - "type": "CoverageCollection", - "domainType": "MultiPoint", - "coverages": [], - "referencing": [ - { - "coordinates": ["x", "y", "z"], - "system": { - "type": "GeographicCRS", - "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - }, - } - ], - "parameters": {}, - } + encoder_obj.add_reference({"coordinates": ["t"], "system": {"type": "TemporalRS", "calendar": "Gregorian"}}) - assert encoder_obj.covjson == covjson + json_string = encoder_obj.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) def test_add_coverage(self): encoder = Eccovjson().encode("CoverageCollection", "wkt") @@ -266,13 +200,15 @@ def test_add_coverage(self): "number": 0, } coords = {} - coords["t"] = ["2017-01-01T00:00:00"] + coords["t"] = ["2017-01-01T00:00:00Z"] coords["composite"] = [[1, 20, 1], [2, 21, 3], [3, 17, 7]] value = {"2t": [111, 222, 333]} encoder.add_coverage(metadata, coords, value) - # print(encoder.covjson) + print(encoder.get_json()) - print(encoder.covjson) + json_string = encoder.pydantic_coverage.model_dump_json(exclude_none=True, indent=4) + assert CoverageCollection.model_validate_json(json_string) + print(json_string) """