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
6 changes: 3 additions & 3 deletions docs/geozarr-minispec.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ This example shows a geospatial Dataset with two spatial data variables. The `za
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
"name": "spatial:",
"description": "Spatial coordinate and transformation information"
"description": "Spatial coordinate information"
}
],
"proj:code": "EPSG:32633",
Expand Down Expand Up @@ -329,7 +329,7 @@ A minimal 2-level geospatial pyramid — well-suited for web mapping or non-EO d
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
"name": "spatial:",
"description": "Spatial coordinate and transformation information"
"description": "Spatial coordinate information"
}
],
"multiscales": {
Expand Down Expand Up @@ -388,7 +388,7 @@ A complete Sentinel-2 L2A scene with 6 resolution levels (variable downsampling
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
"name": "spatial:",
"description": "Spatial coordinate and transformation information"
"description": "Spatial coordinate information"
}
],
"multiscales": {
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies = [
"rioxarray>=0.13.0",
"cf-xarray>=0.8.0",
"typing-extensions>=4.15.0",
"zarr-cm>=0.2.0",
"aiohttp>=3.8.1",
"s3fs>=2024.6.0",
"boto3>=1.34.0",
Expand Down
26 changes: 1 addition & 25 deletions src/eopf_geozarr/data_api/geozarr/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
Any,
Final,
Literal,
NotRequired,
Self,
TypeGuard,
TypeVar,
Expand All @@ -22,7 +21,7 @@
from cf_xarray.utils import parse_cf_standard_name_table
from pydantic import AfterValidator, BaseModel, Field, model_validator
from pydantic.experimental.missing_sentinel import MISSING
from typing_extensions import Protocol, TypedDict, runtime_checkable
from typing_extensions import Protocol, runtime_checkable

from eopf_geozarr.data_api.geozarr.projjson import ProjJSON # noqa: TC001

Expand All @@ -40,29 +39,6 @@ class UNSET_TYPE:
GEO_PROJ_VERSION: Final = "0.1"


class ZarrConventionMetadata(BaseModel):
uuid: str | MISSING = MISSING
schema_url: str | MISSING = MISSING
spec_url: str | MISSING = MISSING
name: str | MISSING = MISSING
description: str | MISSING = MISSING

@model_validator(mode="after")
def ensure_identifiable(self) -> Self:
if self.uuid is MISSING and self.schema_url is MISSING and self.spec_url is MISSING:
raise ValueError("At least one of uuid, schema_url, or spec_url must be provided.")

return self


class ZarrConventionMetadataJSON(TypedDict):
uuid: NotRequired[str]
schema_url: NotRequired[str]
name: NotRequired[str]
description: NotRequired[str]
spec_url: NotRequired[str]


class ProjAttrs(BaseModel, extra="allow"):
"""
Zarr attributes for coordinate reference system (CRS) encoding.
Expand Down
35 changes: 6 additions & 29 deletions src/eopf_geozarr/data_api/geozarr/geoproj.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,16 @@

from __future__ import annotations

from typing import Literal

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

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

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


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"
]
spec_url: Literal["https://github.com/zarr-experimental/geo-proj/blob/v1/README.md"]
description: Literal["Coordinate reference system information for geospatial data"]


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"
)
PROJ_UUID = geo_proj.UUID

# Re-export the zarr-cm TypedDict for the convention metadata object
ProjConvention = geo_proj.GeoProjAttrs


class Proj(BaseModel):
Expand Down
3 changes: 1 addition & 2 deletions src/eopf_geozarr/data_api/geozarr/multiscales/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""Zarr multiscales convention support."""

from .geozarr import MultiscaleGroupAttrs, MultiscaleMeta
from .zcm import MULTISCALE_CONVENTION_METADATA, Multiscales, ScaleLevel, ScaleLevelJSON
from .zcm import Multiscales, ScaleLevel, ScaleLevelJSON

__all__ = [
"MULTISCALE_CONVENTION_METADATA",
"MultiscaleGroupAttrs",
"MultiscaleMeta",
"Multiscales",
Expand Down
5 changes: 2 additions & 3 deletions src/eopf_geozarr/data_api/geozarr/multiscales/geozarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from pydantic import BaseModel, model_validator
from pydantic.experimental.missing_sentinel import MISSING
from typing_extensions import TypedDict

from eopf_geozarr.data_api.geozarr.common import ZarrConventionMetadata # noqa: TC001
from zarr_cm import ConventionMetadataObject # noqa: TC002

from . import tms, zcm

Expand Down Expand Up @@ -56,7 +55,7 @@ class MultiscaleGroupAttrs(BaseModel):
multiscales: MultiscaleAttrs
"""

zarr_conventions: tuple[ZarrConventionMetadata, ...] | MISSING = MISSING
zarr_conventions: tuple[ConventionMetadataObject, ...] | MISSING = MISSING
multiscales: MultiscaleMeta

_zcm_multiscales: zcm.Multiscales | None = None
Expand Down
114 changes: 21 additions & 93 deletions src/eopf_geozarr/data_api/geozarr/multiscales/zcm.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,26 @@
from __future__ import annotations

from typing import Final, Literal, NotRequired

from pydantic import BaseModel, field_validator
from pydantic.experimental.missing_sentinel import MISSING
from typing_extensions import TypedDict

from eopf_geozarr.data_api.geozarr.common import (
ZarrConventionMetadata,
ZarrConventionMetadataJSON,
)

ConventionID = Literal["d35379db-88df-4056-af3a-620245f8e347"]
CONVENTION_ID: Final[ConventionID] = "d35379db-88df-4056-af3a-620245f8e347"

ConventionSchemaURL = Literal[
"https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json"
]
CONVENTION_SCHEMA_URL: Final[ConventionSchemaURL] = (
"https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json"
)

ConventionSpecURL = Literal["https://github.com/zarr-conventions/multiscales/blob/v1/README.md"]
CONVENTION_SPEC_URL: Final[ConventionSpecURL] = (
"https://github.com/zarr-conventions/multiscales/blob/v1/README.md"
)

ConventionDescription = Literal["Multiscale layout of zarr datasets"]
CONVENTION_DESCRIPTION: Final[ConventionDescription] = "Multiscale layout of zarr datasets"

ConventionName = Literal["multiscales"]
CONVENTION_NAME: Final[ConventionName] = "multiscales"


class MultiscaleConventionMetadata(ZarrConventionMetadata):
uuid: ConventionID = CONVENTION_ID
schema_url: ConventionSchemaURL = CONVENTION_SCHEMA_URL
name: ConventionName = CONVENTION_NAME
description: ConventionDescription = CONVENTION_DESCRIPTION
spec_url: ConventionSpecURL = CONVENTION_SPEC_URL


class MultiscaleConventionMetadataJSON(TypedDict):
"""
A TypedDict representation of the Multiscales convention metadata
"""
from zarr_cm import ConventionMetadataObject
from zarr_cm import multiscales as multiscales_cm

uuid: NotRequired[ConventionID]
schema_url: NotRequired[ConventionSchemaURL]
name: NotRequired[ConventionName]
description: NotRequired[ConventionDescription]
spec_url: NotRequired[ConventionSpecURL]
# Convention constants from zarr-cm
CONVENTION_ID = multiscales_cm.UUID
CONVENTION_SCHEMA_URL = multiscales_cm.SCHEMA_URL
CONVENTION_SPEC_URL = multiscales_cm.SPEC_URL
CONVENTION_NAME = multiscales_cm.CMO["name"]
CONVENTION_DESCRIPTION = multiscales_cm.CMO["description"]


# A final dict representation of the Multiscales convention metadata
MULTISCALE_CONVENTION_METADATA: Final[MultiscaleConventionMetadataJSON] = {
"uuid": CONVENTION_ID,
"schema_url": CONVENTION_SCHEMA_URL,
"name": CONVENTION_NAME,
"description": CONVENTION_DESCRIPTION,
"spec_url": CONVENTION_SPEC_URL,
}
# Re-export zarr-cm TypedDicts
TransformJSON = multiscales_cm.Transform
ScaleLevelJSON = multiscales_cm.LayoutObject
MultiscalesJSON = multiscales_cm.MultiscalesAttrs
MultiscalesConventionAttrsJSON = multiscales_cm.MultiscalesConventionAttrs


class ZarrConventionAttrs(BaseModel):
zarr_conventions: tuple[ZarrConventionMetadata, ...]
zarr_conventions: tuple[ConventionMetadataObject, ...]

model_config = {"extra": "allow"}

Expand All @@ -74,11 +30,6 @@ class Transform(BaseModel):
translation: tuple[float, ...] | MISSING = MISSING


class TransformJSON(TypedDict):
scale: NotRequired[tuple[float, ...]]
translation: NotRequired[tuple[float, ...]]


class ScaleLevel(BaseModel):
asset: str
derived_from: str | MISSING = MISSING
Expand All @@ -88,52 +39,29 @@ class ScaleLevel(BaseModel):
model_config = {"extra": "allow"}


class ScaleLevelJSON(TypedDict):
asset: str
derived_from: NotRequired[str]
transform: TransformJSON
resampling_method: NotRequired[str]


class Multiscales(BaseModel):
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]


class MultiscalesAttrs(ZarrConventionAttrs):
multiscales: Multiscales
model_config = {"extra": "allow"}

@field_validator("zarr_conventions", mode="after")
@classmethod
def ensure_multiscales_convention(
cls, value: tuple[ZarrConventionMetadata, ...]
) -> tuple[ZarrConventionMetadata, ...]:
cls, value: tuple[ConventionMetadataObject, ...]
) -> tuple[ConventionMetadataObject, ...]:
"""
Iterate over the elements of zarr_conventions and check that at least one of them is
multiscales
"""
success: bool = False
errors: dict[int, ValueError] = {}
for idx, convention_meta in enumerate(value):
try:
MultiscaleConventionMetadata(**convention_meta.model_dump())
success = True
except ValueError as e:
errors[idx] = e
if not success:
raise ValueError("Multiscales convention not found. Errors: " + str(errors))
expected_uuid = multiscales_cm.CMO["uuid"]
if not any(c["uuid"] == expected_uuid for c in value):
raise ValueError(
f"Multiscales convention (uuid={expected_uuid}) not found in zarr_conventions"
)
return value


class MultiscalesAttrsJSON(TypedDict):
zarr_conventions: tuple[ZarrConventionMetadataJSON, ...]
multiscales: MultiscalesJSON
41 changes: 8 additions & 33 deletions src/eopf_geozarr/data_api/geozarr/spatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,15 @@

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"
)
from zarr_cm import spatial as spatial_cm

from eopf_geozarr.data_api.geozarr.common import is_none

SPATIAL_UUID = spatial_cm.UUID

# Re-export the zarr-cm TypedDict for the convention metadata object
SpatialConvention = spatial_cm.SpatialAttrs


class Spatial(BaseModel):
Expand Down
Loading
Loading