Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions covjsonkit/encoder/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from abc import ABC, abstractmethod
from typing import Any

import numpy as np
import orjson
import pandas as pd
from covjson_pydantic.coverage import CoverageCollection
Expand Down Expand Up @@ -176,7 +177,10 @@ def create_composite_key(date, level, num, para, s):
def handle_non_leaf_node(child):
non_leaf_axes = ["latitude", "longitude", "param", date_key]
if child.axis.name not in non_leaf_axes:
mars_metadata[child.axis.name] = child.values[0]
val = child.values[0]
if isinstance(val, np.datetime64):
val = str(val)
mars_metadata[child.axis.name] = val

def handle_specific_axes(child):
if child.axis.name == "latitude":
Expand Down Expand Up @@ -272,7 +276,10 @@ def create_composite_key_step(date, level, num, para):
def handle_non_leaf_node_step(child):
non_leaf_axes = ["latitude", "longitude", "param", "date", "time"]
if child.axis.name not in non_leaf_axes:
mars_metadata[child.axis.name] = child.values[0]
val = child.values[0]
if isinstance(val, np.datetime64):
val = str(val)
mars_metadata[child.axis.name] = val

def handle_specific_axes_step(child):
if child.axis.name == "latitude":
Expand Down Expand Up @@ -441,7 +448,10 @@ def _ensure_date_keys():
def handle_non_leaf_node_month(child):
non_leaf_axes = ["latitude", "longitude", "param", "year", "month"]
if child.axis.name not in non_leaf_axes:
mars_metadata[child.axis.name] = child.values[0]
val = child.values[0]
if isinstance(val, np.datetime64):
val = str(val)
mars_metadata[child.axis.name] = val

def handle_specific_axes_month(child):
if child.axis.name == "latitude":
Expand Down
2 changes: 1 addition & 1 deletion covjsonkit/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.16"
__version__ = "0.2.17"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ markers = ["data: uses test data (deselect with '-m \"not data\"')",]

[project]
name = "covjsonkit"
version = "0.2.16"
version = "0.2.17"
dependencies = [
"pandas<3",
"orjson",
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

REFORECAST_METADATA_BASE = {
"class": "ce",
"date": np.datetime64("2024-03-01"),
"date": "2024-03-01",
"domain": "g",
"expver": "4321",
"levtype": "sfc",
Expand Down
16 changes: 16 additions & 0 deletions tests/geojsontest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import json
import os

from covjsonkit.api import Covjsonkit

current_dir = os.path.dirname(__file__)
timeseries = os.path.join(current_dir, "data/test_timeseries_coverage.json")
with open(timeseries, "r") as f:
timeseries = json.load(f)


cov = Covjsonkit().decode(timeseries)
ts = cov.to_geojson()
print(ts)
assert ts["type"] == "FeatureCollection"
assert len(ts["features"]) == 16
2 changes: 1 addition & 1 deletion tests/test_encoder_bounding_box_from_polytope.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

EXPECTED_REFORECAST_METADATA = {
"class": "ce",
"date": np.datetime64("2024-03-01"),
"date": "2024-03-01",
"domain": "g",
"expver": "4321",
"levtype": "sfc",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_encoder_position_from_polytope.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_reforecast_single_hdate_two_points(self):

shared_metadata = {
"class": "ce",
"date": np.datetime64("2024-03-01"),
"date": "2024-03-01",
"Forecast date": "2025-07-14T06:00:00Z",
"domain": "g",
"expver": "4321",
Expand Down Expand Up @@ -168,7 +168,7 @@ def test_reforecast_two_hdates_two_points(self):

shared_metadata = {
"class": "ce",
"date": np.datetime64("2024-03-01"),
"date": "2024-03-01",
"domain": "g",
"expver": "4321",
"levtype": "sfc",
Expand Down
47 changes: 43 additions & 4 deletions tests/test_encoder_time_series_from_polytope.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import json

import numpy as np
import orjson
import pytest
from conftest import chain, make_leaf, make_point, node, tip
from polytope_feature.datacube.tensor_index_tree import TensorIndexTree

Expand Down Expand Up @@ -29,7 +33,7 @@ def hdate_branch(hdate, lat, lon, result):

EXPECTED_HDATE_METADATA = {
"class": "ce",
"date": np.datetime64("2024-03-01"),
"date": "2024-03-01",
"domain": "g",
"expver": "4321",
"levtype": "sfc",
Expand Down Expand Up @@ -199,12 +203,13 @@ def test_multiple_params(self):


class TestTimeseriesFromPolytopeReforecast:
def test_single_point(self):
@pytest.mark.parametrize("date", [np.datetime64("2024-03-01"), np.datetime64("2024-03-01T00:00:00")])
def test_single_point(self, date):
# 1 hdate (with time pre-merged by polytope-mars), 1 point
tree = chain(
TensorIndexTree(),
node("class", ("ce",)),
node("date", (np.datetime64("2024-03-01"),)),
node("date", (date,)),
hdate_branch(np.datetime64("2025-07-14T06:00:00"), 51.5, 6.5, [42.17]),
)

Expand All @@ -231,7 +236,11 @@ def test_single_point(self):
}
}

assert cov["mars:metadata"] == {"Forecast date": "2025-07-14T06:00:00Z", **EXPECTED_HDATE_METADATA}
assert cov["mars:metadata"] == {
"Forecast date": "2025-07-14T06:00:00Z",
**EXPECTED_HDATE_METADATA,
"date": date.astype(str),
}

def test_multiple_times(self):
# 2 hdate values (pre-merged times from same day), 1 point → 2 coverages
Expand Down Expand Up @@ -453,3 +462,33 @@ def test_multiple_hdates_and_steps(self):
assert cov["domain"]["axes"]["t"]["values"] == t
assert cov["ranges"]["dis06"]["values"] == vals
assert cov["mars:metadata"] == {"Forecast date": fc_date, **EXPECTED_HDATE_METADATA}

@pytest.mark.parametrize(
"json_module,date", [(json, np.datetime64("2024-03-01")), (orjson, np.datetime64("2024-03-01T00:00:00"))]
)
def test_reforecast_covjson_is_json_serialisable(self, json_module, date):
"""CoverageJSON produced by from_polytope_reforecast must be serialisable
with both stdlib json and orjson (i.e. no numpy.datetime64 left in the
mars:metadata dict — regression test for the date axis leak)."""
tree = chain(
TensorIndexTree(),
node("class", ("ce",)),
node("date", (date,)),
hdate_branch(np.datetime64("2025-07-14T06:00:00"), 51.5, 6.5, [42.17]),
)

covjson = Covjsonkit().encode("CoverageCollection", "PointSeries").from_polytope_reforecast(tree)

# stdlib json.dumps must not raise TypeError
serialised = json_module.dumps(covjson)

# The 'date' value in mars:metadata must be a plain string, not numpy.datetime64
for cov in covjson["coverages"]:
date_val = cov["mars:metadata"].get("date")
assert not isinstance(
date_val, np.datetime64
), f"mars:metadata['date'] is still a numpy.datetime64: {date_val!r}"

# Round-trip through the JSON module to ensure it can be deserialised back to a dict
deserialised = json_module.loads(serialised)
assert deserialised == covjson
2 changes: 1 addition & 1 deletion tests/test_encoder_vertical_profile_from_polytope.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class TestVerticalProfileFromPolytopeReforecast:

EXPECTED_REFORECAST_METADATA = {
"class": "ce",
"date": np.datetime64("2024-03-01"),
"date": "2024-03-01",
"domain": "g",
"expver": "4321",
"levtype": "pl",
Expand Down
Loading