From 098cf1536f0a04c16a1df909e0ef8dacb32451e0 Mon Sep 17 00:00:00 2001 From: Nikolay Koldunov Date: Wed, 11 Feb 2026 13:31:06 +0100 Subject: [PATCH 1/3] Fix TimeSeries.to_geojson using hardcoded axis names The to_geojson method hardcoded "latitude", "longitude", and "levelist" as axis names instead of using self.x_name, self.y_name, and self.z_name which are already resolved in __init__ to handle both naming conventions (x/y/z and latitude/longitude/levelist). This caused a KeyError when the CovJSON data used "x", "y", "z" as axis names (e.g. data from certain Polytope/DestinationEarth climate-dt requests). Co-Authored-By: Claude Opus 4.6 --- covjsonkit/decoder/TimeSeries.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/covjsonkit/decoder/TimeSeries.py b/covjsonkit/decoder/TimeSeries.py index 4b11bed..da4794e 100644 --- a/covjsonkit/decoder/TimeSeries.py +++ b/covjsonkit/decoder/TimeSeries.py @@ -75,9 +75,9 @@ def to_geotiff(self): def to_geojson(self): features = [] for coverage in self.covjson["coverages"]: - lat = coverage["domain"]["axes"]["latitude"]["values"][0] - lon = coverage["domain"]["axes"]["longitude"]["values"][0] - z = coverage["domain"]["axes"]["levelist"]["values"][0] + lat = coverage["domain"]["axes"][self.x_name]["values"][0] + lon = coverage["domain"]["axes"][self.y_name]["values"][0] + z = coverage["domain"]["axes"][self.z_name]["values"][0] datetimes = coverage["domain"]["axes"]["t"]["values"] if "mars:metadata" in coverage: mars_metadata = coverage["mars:metadata"] From 44e114419bbc687d509f26c652c133f2bdb79bf6 Mon Sep 17 00:00:00 2001 From: awarde96 Date: Wed, 11 Feb 2026 13:02:20 +0000 Subject: [PATCH 2/3] Update axis to be generic for vertical profile and position --- covjsonkit/decoder/Position.py | 6 +++--- covjsonkit/decoder/VerticalProfile.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/covjsonkit/decoder/Position.py b/covjsonkit/decoder/Position.py index fa94749..1fc5a5f 100644 --- a/covjsonkit/decoder/Position.py +++ b/covjsonkit/decoder/Position.py @@ -75,9 +75,9 @@ def to_geotiff(self): def to_geojson(self): features = [] for coverage in self.covjson["coverages"]: - lat = coverage["domain"]["axes"]["latitude"]["values"][0] - lon = coverage["domain"]["axes"]["longitude"]["values"][0] - z = coverage["domain"]["axes"]["levelist"]["values"][0] + lat = coverage["domain"]["axes"][self.x_name]["values"][0] + lon = coverage["domain"]["axes"][self.y_name]["values"][0] + z = coverage["domain"]["axes"][self.z_name]["values"][0] datetimes = coverage["domain"]["axes"]["t"]["values"] if "mars:metadata" in coverage: mars_metadata = coverage["mars:metadata"] diff --git a/covjsonkit/decoder/VerticalProfile.py b/covjsonkit/decoder/VerticalProfile.py index 6bcb18b..77b161b 100644 --- a/covjsonkit/decoder/VerticalProfile.py +++ b/covjsonkit/decoder/VerticalProfile.py @@ -73,9 +73,9 @@ def to_geotiff(self): def to_geojson(self): features = [] for coverage in self.covjson["coverages"]: - lat = coverage["domain"]["axes"]["latitude"]["values"][0] - lon = coverage["domain"]["axes"]["longitude"]["values"][0] - levels = coverage["domain"]["axes"]["levelist"]["values"] + lat = coverage["domain"]["axes"][self.x_name]["values"][0] + lon = coverage["domain"]["axes"][self.y_name]["values"][0] + levels = coverage["domain"]["axes"][self.z_name]["values"] datetimes = coverage["domain"]["axes"]["t"]["values"] if "mars:metadata" in coverage: mars_metadata = coverage["mars:metadata"] From 13bb6da2ec9406d6e39b5f92d84ab674d1867684 Mon Sep 17 00:00:00 2001 From: awarde96 Date: Wed, 11 Feb 2026 13:12:20 +0000 Subject: [PATCH 3/3] Add tests for x, y, z axes --- covjsonkit/version.py | 2 +- tests/data/test_timeseries_xyz_coverage.json | 351 +++++++++++ tests/data/test_verticalprofile_coverage.json | 573 +++++++++++++++++- .../test_verticalprofile_xyz_coverage.json | 572 +++++++++++++++++ tests/test_geojson.py | 21 + 5 files changed, 1517 insertions(+), 2 deletions(-) create mode 100644 tests/data/test_timeseries_xyz_coverage.json create mode 100644 tests/data/test_verticalprofile_xyz_coverage.json diff --git a/covjsonkit/version.py b/covjsonkit/version.py index 6232f7a..5635676 100644 --- a/covjsonkit/version.py +++ b/covjsonkit/version.py @@ -1 +1 @@ -__version__ = "0.2.10" +__version__ = "0.2.11" diff --git a/tests/data/test_timeseries_xyz_coverage.json b/tests/data/test_timeseries_xyz_coverage.json new file mode 100644 index 0000000..3e0718d --- /dev/null +++ b/tests/data/test_timeseries_xyz_coverage.json @@ -0,0 +1,351 @@ +{ + "type": "CoverageCollection", + "domainType": "PointSeries", + "coverages": [ + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levtype": "sfc", + "number": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 0 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z", + "2025-06-23T01:00:00Z", + "2025-06-23T02:00:00Z", + "2025-06-23T03:00:00Z" + ] + } + } + }, + "ranges": { + "tcc": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "tcc" + ], + "values": [ + 0.31280517578125, + 0.45086669921875, + 0.421661376953125, + 0.233123779296875 + ] + }, + "2t": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "2t" + ], + "values": [ + 299.13653564453125, + 299.2686462402344, + 298.88636779785156, + 298.9047088623047 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levtype": "sfc", + "number": 2, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 0 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z", + "2025-06-23T01:00:00Z", + "2025-06-23T02:00:00Z", + "2025-06-23T03:00:00Z" + ] + } + } + }, + "ranges": { + "tcc": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "tcc" + ], + "values": [ + 0.831947922706604, + 0.763214111328125, + 0.762786865234375, + 0.990081787109375 + ] + }, + "2t": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "2t" + ], + "values": [ + 298.76365661621094, + 298.6814422607422, + 298.86614990234375, + 298.78407287597656 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levtype": "sfc", + "number": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.769770651632 + ] + }, + "longitude": { + "values": [ + 350.914051841746 + ] + }, + "levelist": { + "values": [ + 0 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z", + "2025-06-23T01:00:00Z", + "2025-06-23T02:00:00Z", + "2025-06-23T03:00:00Z" + ] + } + } + }, + "ranges": { + "tcc": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "tcc" + ], + "values": [ + 1.0, + 1.0, + 0.205352783203125, + 0.28228759765625 + ] + }, + "2t": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "2t" + ], + "values": [ + 292.08770751953125, + 292.0010681152344, + 292.27699279785156, + 292.2133026123047 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levtype": "sfc", + "number": 2, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.769770651632 + ] + }, + "longitude": { + "values": [ + 350.914051841746 + ] + }, + "levelist": { + "values": [ + 0 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z", + "2025-06-23T01:00:00Z", + "2025-06-23T02:00:00Z", + "2025-06-23T03:00:00Z" + ] + } + } + }, + "ranges": { + "tcc": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "tcc" + ], + "values": [ + 0.9999929666519165, + 1.0, + 0.590179443359375, + 0.099029541015625 + ] + }, + "2t": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 4 + ], + "axisNames": [ + "2t" + ], + "values": [ + 292.56053161621094, + 292.1736297607422, + 291.95794677734375, + 291.95594787597656 + ] + } + } + } + ], + "referencing": [ + { + "coordinates": [ + "x", + "y", + "z" + ], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84" + } + } + ], + "parameters": { + "tcc": { + "type": "Parameter", + "description": { + "en": "This parameter is the proportion of a grid box covered by cloud. Total cloud cover is a single level field calculated from the cloud occurring at different model levels through the atmosphere. Assumptions are made about the degree of overlap/randomness between clouds at different heights.

Cloud fractions vary from 0 to 1.\n
[NOTE: See 228164 for the equivalent parameter in \"%\"]" + }, + "unit": { + "symbol": "(0 - 1)" + }, + "observedProperty": { + "id": "tcc", + "label": { + "en": "Total cloud cover" + } + } + }, + "2t": { + "type": "Parameter", + "description": { + "en": "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 (\u00b0C) 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" + } + } + } + } +} \ No newline at end of file diff --git a/tests/data/test_verticalprofile_coverage.json b/tests/data/test_verticalprofile_coverage.json index c9d892c..ed71891 100644 --- a/tests/data/test_verticalprofile_coverage.json +++ b/tests/data/test_verticalprofile_coverage.json @@ -1 +1,572 @@ -{"type": "CoverageCollection", "domainType": "VerticalProfile", "coverages": [{"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 1, "step": 0, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [-0.035149384216]}, "longitude": {"values": [0.981308411215]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T00:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.482228177948855e-06, 4.444737896847073e-06, 4.3706095311790705e-06, 4.0073391573969275e-06, 3.6435558286029845e-06, 3.2262623790302314e-06, 3.0683795557706617e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 1, "step": 1, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [-0.035149384216]}, "longitude": {"values": [0.981308411215]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T01:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.487486876314506e-06, 4.4314983824733645e-06, 4.3832060327986255e-06, 4.02079604100436e-06, 3.627267687988933e-06, 3.233985808037687e-06, 3.054315129702445e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 2, "step": 0, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [-0.035149384216]}, "longitude": {"values": [0.981308411215]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T00:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.201782076052041e-06, 4.282088411855511e-06, 4.331615855335258e-06, 4.028772309538908e-06, 3.6359106161398813e-06, 3.2297457437380217e-06, 3.0871296985424124e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 2, "step": 1, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [-0.035149384216]}, "longitude": {"values": [0.981308411215]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T01:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.185261843758781e-06, 4.284713213564828e-06, 4.3478394218254834e-06, 4.017736500827596e-06, 3.635374923760537e-06, 3.232900780858472e-06, 3.0775609047850594e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 1, "step": 0, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [38.910368186756]}, "longitude": {"values": [350.889192886457]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T00:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.3810632632812485e-06, 4.301430635678116e-06, 4.141271347180009e-06, 3.946424840250984e-06, 3.849378117593005e-06, 3.714595550263766e-06, 3.350861334183719e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 1, "step": 1, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [38.910368186756]}, "longitude": {"values": [350.889192886457]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T01:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.363620973890647e-06, 4.310193617129698e-06, 4.14641726820264e-06, 3.9454171201214194e-06, 3.844673301500734e-06, 3.720980203070212e-06, 3.3482638173154555e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 2, "step": 0, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [38.910368186756]}, "longitude": {"values": [350.889192886457]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T00:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.255100293448777e-06, 4.277315383660607e-06, 4.128412911086343e-06, 3.9497263060184196e-06, 3.843391823465936e-06, 3.71234546037158e-06, 3.364314579812344e-06]}}}, {"mars:metadata": {"class": "od", "Forecast date": "2025-06-23T00:00:00Z", "domain": "g", "expver": "0001", "levelist": 1, "levtype": "pl", "number": 2, "step": 1, "stream": "enfo", "type": "pf"}, "type": "Coverage", "domain": {"type": "Domain", "axes": {"latitude": {"values": [38.910368186756]}, "longitude": {"values": [350.889192886457]}, "levelist": {"values": [1, 2, 3, 5, 7, 10, 20]}, "t": {"values": ["2025-06-23T01:00:00Z"]}}}, "ranges": {"q": {"type": "NdArray", "dataType": "float", "shape": [7], "axisNames": ["levelist"], "values": [4.274668810921867e-06, 4.271092620911077e-06, 4.144170816289261e-06, 3.943900082958862e-06, 3.83627866540337e-06, 3.7143654481042176e-06, 3.3630112739047036e-06]}}}], "referencing": [{"coordinates": ["latitude", "longitude", "levelist"], "system": {"type": "GeographicCRS", "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"}}], "parameters": {"q": {"type": "Parameter", "description": {"en": "This parameter is the mass of water vapour per kilogram of moist air.

The total mass of moist air is the sum of the dry air, water vapour, cloud liquid, cloud ice, rain and falling snow."}, "unit": {"symbol": "kg kg**-1"}, "observedProperty": {"id": "q", "label": {"en": "Specific humidity"}}}}} +{ + "type": "CoverageCollection", + "domainType": "VerticalProfile", + "coverages": [ + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.482228177948855e-06, + 4.444737896847073e-06, + 4.3706095311790705e-06, + 4.0073391573969275e-06, + 3.6435558286029845e-06, + 3.2262623790302314e-06, + 3.0683795557706617e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.487486876314506e-06, + 4.4314983824733645e-06, + 4.3832060327986255e-06, + 4.02079604100436e-06, + 3.627267687988933e-06, + 3.233985808037687e-06, + 3.054315129702445e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.201782076052041e-06, + 4.282088411855511e-06, + 4.331615855335258e-06, + 4.028772309538908e-06, + 3.6359106161398813e-06, + 3.2297457437380217e-06, + 3.0871296985424124e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.185261843758781e-06, + 4.284713213564828e-06, + 4.3478394218254834e-06, + 4.017736500827596e-06, + 3.635374923760537e-06, + 3.232900780858472e-06, + 3.0775609047850594e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.3810632632812485e-06, + 4.301430635678116e-06, + 4.141271347180009e-06, + 3.946424840250984e-06, + 3.849378117593005e-06, + 3.714595550263766e-06, + 3.350861334183719e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.363620973890647e-06, + 4.310193617129698e-06, + 4.14641726820264e-06, + 3.9454171201214194e-06, + 3.844673301500734e-06, + 3.720980203070212e-06, + 3.3482638173154555e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.255100293448777e-06, + 4.277315383660607e-06, + 4.128412911086343e-06, + 3.9497263060184196e-06, + 3.843391823465936e-06, + 3.71234546037158e-06, + 3.364314579812344e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.274668810921867e-06, + 4.271092620911077e-06, + 4.144170816289261e-06, + 3.943900082958862e-06, + 3.83627866540337e-06, + 3.7143654481042176e-06, + 3.3630112739047036e-06 + ] + } + } + } + ], + "referencing": [ + { + "coordinates": [ + "latitude", + "longitude", + "levelist" + ], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84" + } + } + ], + "parameters": { + "q": { + "type": "Parameter", + "description": { + "en": "This parameter is the mass of water vapour per kilogram of moist air.

The total mass of moist air is the sum of the dry air, water vapour, cloud liquid, cloud ice, rain and falling snow." + }, + "unit": { + "symbol": "kg kg**-1" + }, + "observedProperty": { + "id": "q", + "label": { + "en": "Specific humidity" + } + } + } + } +} \ No newline at end of file diff --git a/tests/data/test_verticalprofile_xyz_coverage.json b/tests/data/test_verticalprofile_xyz_coverage.json new file mode 100644 index 0000000..28b7f2f --- /dev/null +++ b/tests/data/test_verticalprofile_xyz_coverage.json @@ -0,0 +1,572 @@ +{ + "type": "CoverageCollection", + "domainType": "VerticalProfile", + "coverages": [ + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.482228177948855e-06, + 4.444737896847073e-06, + 4.3706095311790705e-06, + 4.0073391573969275e-06, + 3.6435558286029845e-06, + 3.2262623790302314e-06, + 3.0683795557706617e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.487486876314506e-06, + 4.4314983824733645e-06, + 4.3832060327986255e-06, + 4.02079604100436e-06, + 3.627267687988933e-06, + 3.233985808037687e-06, + 3.054315129702445e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.201782076052041e-06, + 4.282088411855511e-06, + 4.331615855335258e-06, + 4.028772309538908e-06, + 3.6359106161398813e-06, + 3.2297457437380217e-06, + 3.0871296985424124e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + -0.035149384216 + ] + }, + "longitude": { + "values": [ + 0.981308411215 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.185261843758781e-06, + 4.284713213564828e-06, + 4.3478394218254834e-06, + 4.017736500827596e-06, + 3.635374923760537e-06, + 3.232900780858472e-06, + 3.0775609047850594e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.3810632632812485e-06, + 4.301430635678116e-06, + 4.141271347180009e-06, + 3.946424840250984e-06, + 3.849378117593005e-06, + 3.714595550263766e-06, + 3.350861334183719e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 1, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.363620973890647e-06, + 4.310193617129698e-06, + 4.14641726820264e-06, + 3.9454171201214194e-06, + 3.844673301500734e-06, + 3.720980203070212e-06, + 3.3482638173154555e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 0, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T00:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.255100293448777e-06, + 4.277315383660607e-06, + 4.128412911086343e-06, + 3.9497263060184196e-06, + 3.843391823465936e-06, + 3.71234546037158e-06, + 3.364314579812344e-06 + ] + } + } + }, + { + "mars:metadata": { + "class": "od", + "Forecast date": "2025-06-23T00:00:00Z", + "domain": "g", + "expver": "0001", + "levelist": 1, + "levtype": "pl", + "number": 2, + "step": 1, + "stream": "enfo", + "type": "pf" + }, + "type": "Coverage", + "domain": { + "type": "Domain", + "axes": { + "latitude": { + "values": [ + 38.910368186756 + ] + }, + "longitude": { + "values": [ + 350.889192886457 + ] + }, + "levelist": { + "values": [ + 1, + 2, + 3, + 5, + 7, + 10, + 20 + ] + }, + "t": { + "values": [ + "2025-06-23T01:00:00Z" + ] + } + } + }, + "ranges": { + "q": { + "type": "NdArray", + "dataType": "float", + "shape": [ + 7 + ], + "axisNames": [ + "levelist" + ], + "values": [ + 4.274668810921867e-06, + 4.271092620911077e-06, + 4.144170816289261e-06, + 3.943900082958862e-06, + 3.83627866540337e-06, + 3.7143654481042176e-06, + 3.3630112739047036e-06 + ] + } + } + } + ], + "referencing": [ + { + "coordinates": [ + "x", + "y", + "z" + ], + "system": { + "type": "GeographicCRS", + "id": "http://www.opengis.net/def/crs/OGC/1.3/CRS84" + } + } + ], + "parameters": { + "q": { + "type": "Parameter", + "description": { + "en": "This parameter is the mass of water vapour per kilogram of moist air.

The total mass of moist air is the sum of the dry air, water vapour, cloud liquid, cloud ice, rain and falling snow." + }, + "unit": { + "symbol": "kg kg**-1" + }, + "observedProperty": { + "id": "q", + "label": { + "en": "Specific humidity" + } + } + } + } +} \ No newline at end of file diff --git a/tests/test_geojson.py b/tests/test_geojson.py index 8df5dab..a684aef 100644 --- a/tests/test_geojson.py +++ b/tests/test_geojson.py @@ -11,10 +11,18 @@ def setup_method(self): with open(timeseries, "r") as f: self.timeseries = json.load(f) + timeseries_xyz = os.path.join(current_dir, "data/test_timeseries_xyz_coverage.json") + with open(timeseries_xyz, "r") as f: + self.timeseries_xyz = json.load(f) + verticalprofile = os.path.join(current_dir, "data/test_verticalprofile_coverage.json") with open(verticalprofile, "r") as f: self.verticalprofile = json.load(f) + verticalprofile_xyz = os.path.join(current_dir, "data/test_verticalprofile_xyz_coverage.json") + with open(verticalprofile_xyz, "r") as f: + self.verticalprofile_xyz = json.load(f) + path = os.path.join(current_dir, "data/test_path_coverage.json") with open(path, "r") as f: self.path = json.load(f) @@ -29,12 +37,25 @@ def test_geojson_timeseries(self): assert ts["type"] == "FeatureCollection" assert len(ts["features"]) == 16 + + def test_geojson_xyz_axes_timeseries(self): + cov = Covjsonkit().decode(self.timeseries_xyz) + ts = cov.to_geojson() + assert ts["type"] == "FeatureCollection" + assert len(ts["features"]) == 16 + def test_geojson_verticalprofile(self): cov = Covjsonkit().decode(self.verticalprofile) vp = cov.to_geojson() assert vp["type"] == "FeatureCollection" assert len(vp["features"]) == 56 + def test_geojson_xyz_axes_verticalprofile(self): + cov = Covjsonkit().decode(self.verticalprofile_xyz) + vp = cov.to_geojson() + assert vp["type"] == "FeatureCollection" + assert len(vp["features"]) == 56 + def test_geojson_path(self): cov = Covjsonkit().decode(self.path) path = cov.to_geojson()