Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4528bb6
feat: add Spatial Zarr Convention models and metadata
emmanuelmathot Dec 16, 2025
4490403
fix: enhance multiscales metadata handling with fallback for bounds a…
emmanuelmathot Dec 16, 2025
94ba321
Update eopf-geozarr version to 0.5.1.dev18+g4528bb6b0.d20251216 in uv…
emmanuelmathot Dec 16, 2025
79cf61a
fix: improve formatting and consistency in geospatial metadata classes
emmanuelmathot Dec 16, 2025
6e853cb
feat: add spatial properties to overview level metadata and update ty…
emmanuelmathot Dec 17, 2025
e88d4ea
feat: implement tests for Spatial Zarr Convention models and metadata
emmanuelmathot Dec 17, 2025
7b52696
feat: enhance Proj and ProjConventionMetadata tests for serialization…
emmanuelmathot Dec 17, 2025
4449dd8
feat: add validation for non-empty dimensions in Spatial model
emmanuelmathot Dec 17, 2025
9f184a7
fix: remove redundant imports of ProjConventionMetadata and SpatialCo…
emmanuelmathot Dec 17, 2025
c436412
feat: add spatial properties and projection information to Zarr conve…
emmanuelmathot Dec 17, 2025
44aaa2d
feat: update spatial metadata and derivation chains in Zarr examples …
emmanuelmathot Dec 17, 2025
9a42275
fix: remove redundant version field from MultiscaleMeta and Multiscal…
emmanuelmathot Dec 17, 2025
32a425b
refactor: streamline spatial metadata calculation and error handling …
emmanuelmathot Dec 17, 2025
a505939
fix: remove redundant version field from multiscales and optimized ge…
emmanuelmathot Dec 17, 2025
40bd7bf
fix: improve transform calculation logging for multiscales metadata
emmanuelmathot Dec 17, 2025
00630c8
fix: add validation for spatial transform data to prevent all-zero en…
emmanuelmathot Dec 17, 2025
41a1665
fix: remove redundant spatial transform entries from optimized geozar…
emmanuelmathot Dec 17, 2025
1b88c53
fix: allow spatial_transform to be None in OverviewLevelJSON type def…
emmanuelmathot Dec 17, 2025
212108e
fix: refactor ScaleLevel initialization and remove redundant spatial_…
emmanuelmathot Dec 17, 2025
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
676 changes: 359 additions & 317 deletions .vscode/launch.json

Large diffs are not rendered by default.

61 changes: 34 additions & 27 deletions src/eopf_geozarr/data_api/geozarr/geoproj.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,61 @@
"""
Models for the GeoProj Zarr Convention
Models for the Proj Zarr Convention (v1.0)
"""

from __future__ import annotations

from typing import Literal, Self
from typing import Literal

from pydantic import BaseModel, Field, model_validator
from typing_extensions import TypedDict

from eopf_geozarr.data_api.geozarr.common import is_none
from eopf_geozarr.data_api.geozarr.common import ZarrConventionMetadata, is_none
from eopf_geozarr.data_api.geozarr.projjson import ProjJSON # noqa: TC001

GEO_PROJ_UUID: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"] = (
"f17cb550-5864-4468-aeb7-f3180cfb622f"
)
PROJ_UUID: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"] = "f17cb550-5864-4468-aeb7-f3180cfb622f"


class GeoProjConvention(TypedDict):
version: Literal["0.1.0"]
schema: Literal[
"https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v0.1.0/schema.json"
class ProjConvention(TypedDict):
uuid: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"]
name: Literal["proj:"]
schema_url: Literal[
"https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json"
]
name: Literal["geo-proj"]
spec_url: Literal["https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"]
description: Literal["Coordinate reference system information for geospatial data"]
spec: Literal["https://github.com/zarr-experimental/geo-proj/blob/v0.1.0/README.md"]


GeoProjConventions = TypedDict( # type: ignore[misc]
"GeoProjConventions", {GEO_PROJ_UUID: GeoProjConvention}, closed=False
)
class ProjConventionMetadata(ZarrConventionMetadata):
uuid: Literal["f17cb550-5864-4468-aeb7-f3180cfb622f"] = PROJ_UUID
name: Literal["proj:"] = "proj:"
schema_url: Literal[
"https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json"
] = "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json"
spec_url: Literal["https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"] = (
"https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"
)
description: Literal["Coordinate reference system information for geospatial data"] = (
"Coordinate reference system information for geospatial data"
)


class GeoProj(BaseModel):
class Proj(BaseModel):
# At least one of code, wkt2, or projjson must be provided
code: str | None = Field(None, alias="proj:code", exclude_if=is_none)
wkt2: str | None = Field(None, alias="proj:wkt2", exclude_if=is_none)
projjson: ProjJSON | None = Field(None, alias="proj:projjson", exclude_if=is_none)
spatial_dimensions: tuple[str, str] = Field(alias="proj:spatial_dimensions")
transform: tuple[float, float, float, float, float, float] | None = Field(
None, alias="proj:transform", exclude_if=is_none
)
bbox: tuple[float, float, float, float] | None = Field(
None, alias="proj:bbox", exclude_if=is_none
)
shape: tuple[int, int] | None = Field(None, alias="proj:shape", exclude_if=is_none)

model_config = {"extra": "allow", "serialize_by_alias": True}

@model_validator(mode="after")
def ensure_required_conditional_attributes(self) -> Self:
if self.code is None and self.wkt2 is None and self.projjson is None:
raise ValueError("One of 'code', 'wkt2', or 'projjson' must be provided.")
def validate_at_least_one_crs(self) -> Proj:
"""Validate that at least one CRS field is provided"""
if not any([self.code, self.wkt2, self.projjson]):
raise ValueError(
"At least one of proj:code, proj:wkt2, or proj:projjson must be provided"
)
return self


# Backwards compatibility alias
GeoProj = Proj
1 change: 1 addition & 0 deletions src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class MultiscaleMeta(BaseModel):
or ZCM multiscale metadata
"""

version: str | MISSING = MISSING
layout: tuple[zcm.ScaleLevel, ...] | MISSING = MISSING
resampling_method: str | MISSING = MISSING
tile_matrix_set: tms.TileMatrixSet | MISSING = MISSING
Expand Down
2 changes: 2 additions & 0 deletions src/eopf_geozarr/data_api/geozarr/multiscales/zcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,15 @@ class ScaleLevelJSON(TypedDict):


class Multiscales(BaseModel):
version: str | MISSING = MISSING
layout: tuple[ScaleLevel, ...]
resampling_method: str | MISSING = MISSING

model_config = {"extra": "allow"}


class MultiscalesJSON(TypedDict):
version: NotRequired[str]
layout: tuple[ScaleLevelJSON, ...]
resampling_method: NotRequired[str]

Expand Down
58 changes: 58 additions & 0 deletions src/eopf_geozarr/data_api/geozarr/spatial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Models for the Spatial Zarr Convention
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field, model_validator
from typing_extensions import TypedDict

from eopf_geozarr.data_api.geozarr.common import ZarrConventionMetadata, is_none

SPATIAL_UUID: Literal["689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"] = (
"689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"
)


class SpatialConvention(TypedDict):
uuid: Literal["689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"]
name: Literal["spatial:"]
schema_url: Literal[
"https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json"
]
spec_url: Literal["https://github.com/zarr-conventions/spatial/blob/v1/README.md"]
description: Literal["Spatial coordinate and transformation information"]


class SpatialConventionMetadata(ZarrConventionMetadata):
uuid: Literal["689b58e2-cf7b-45e0-9fff-9cfc0883d6b4"] = SPATIAL_UUID
name: Literal["spatial:"] = "spatial:"
schema_url: Literal[
"https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json"
] = "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json"
spec_url: Literal["https://github.com/zarr-conventions/spatial/blob/v1/README.md"] = (
"https://github.com/zarr-conventions/spatial/blob/v1/README.md"
)
description: Literal["Spatial coordinate and transformation information"] = (
"Spatial coordinate and transformation information"
)


class Spatial(BaseModel):
dimensions: list[str] = Field(alias="spatial:dimensions") # Required field
bbox: list[float] | None = Field(None, alias="spatial:bbox", exclude_if=is_none)
transform_type: str = Field("affine", alias="spatial:transform_type")
transform: list[float] | None = Field(None, alias="spatial:transform", exclude_if=is_none)
shape: list[int] | None = Field(None, alias="spatial:shape", exclude_if=is_none)
registration: str = Field("pixel", alias="spatial:registration")

model_config = {"extra": "allow", "serialize_by_alias": True}

@model_validator(mode="after")
def validate_dimensions_not_empty(self) -> Spatial:
"""Validate that dimensions list is not empty."""
if not self.dimensions:
raise ValueError("spatial:dimensions must contain at least one dimension")
return self
Loading
Loading