From 1628a9e4622d457ee7b2faa42e1f9d91268f68ba Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Mon, 29 Sep 2025 15:34:43 +0200 Subject: [PATCH 1/3] projjson pydantic model --- src/eopf_geozarr/data_api/geozarr/common.py | 17 + src/eopf_geozarr/data_api/geozarr/projjson.py | 690 + tests/test_data_api/conftest.py | 14240 +--------------- .../geozarr_examples/sentinel_2.json | 14142 +++++++++++++++ .../projjson_examples/bound_crs.json | 198 + .../projjson_examples/compound_crs.json | 123 + .../projjson_examples/datum_ensemble.json | 98 + .../explicit_prime_meridian.json | 47 + .../implicit_prime_meridian.json | 43 + .../projjson_examples/projected_crs.json | 124 + .../projjson_examples/transformation.json | 112 + tests/test_data_api/test_common.py | 4 +- tests/test_data_api/test_projjson.py | 789 + tests/test_data_api/test_v3.py | 6 +- tests/test_projjson.py | 0 15 files changed, 16478 insertions(+), 14155 deletions(-) create mode 100644 src/eopf_geozarr/data_api/geozarr/projjson.py create mode 100644 tests/test_data_api/geozarr_examples/sentinel_2.json create mode 100644 tests/test_data_api/projjson_examples/bound_crs.json create mode 100644 tests/test_data_api/projjson_examples/compound_crs.json create mode 100644 tests/test_data_api/projjson_examples/datum_ensemble.json create mode 100644 tests/test_data_api/projjson_examples/explicit_prime_meridian.json create mode 100644 tests/test_data_api/projjson_examples/implicit_prime_meridian.json create mode 100644 tests/test_data_api/projjson_examples/projected_crs.json create mode 100644 tests/test_data_api/projjson_examples/transformation.json create mode 100644 tests/test_data_api/test_projjson.py create mode 100644 tests/test_projjson.py diff --git a/src/eopf_geozarr/data_api/geozarr/common.py b/src/eopf_geozarr/data_api/geozarr/common.py index ee21ee3..29a4998 100644 --- a/src/eopf_geozarr/data_api/geozarr/common.py +++ b/src/eopf_geozarr/data_api/geozarr/common.py @@ -13,6 +13,23 @@ from eopf_geozarr.data_api.geozarr.types import ResamplingMethod +class ProjAttrs(BaseModel, extra="allow"): + """ + Zarr attributes for coordinate reference system (CRS) encoding. + + Attributes + version: str + The version of the metadata. + code: str | None + Authority:Code identifier. + wkt2 : str | None + WKT2 (ISO 19162) representation of the CRS. + projjson: ProjJson | None + PROJJSON representation of the CRS. + bbox: + """ + + class BaseDataArrayAttrs(BaseModel, extra="allow"): """ Base attributes for a GeoZarr DataArray. diff --git a/src/eopf_geozarr/data_api/geozarr/projjson.py b/src/eopf_geozarr/data_api/geozarr/projjson.py new file mode 100644 index 0000000..8da916d --- /dev/null +++ b/src/eopf_geozarr/data_api/geozarr/projjson.py @@ -0,0 +1,690 @@ +""" +Pydantic models for PROJ JSON schema v0.7 + +Based on the schema at: https://proj.org/en/latest/schemas/v0.7/projjson.schema.json +""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, model_validator + + +class Id(BaseModel): + authority: str + code: str | int + version: str | float | None = None + authority_citation: str | None = None + uri: str | None = None + + +class Unit(BaseModel): + type: Literal[ + "Unit", "AngularUnit", "LinearUnit", "ScaleUnit", "ParametricUnit", "TimeUnit" + ] = "Unit" + name: str + conversion_factor: float + id: Id | None = None + ids: list[Id] | None = None + + @model_validator(mode="after") + def validate_id_mutually_exclusive(self) -> Unit: + """Ensure that id and ids are mutually exclusive.""" + if self.id is not None and self.ids is not None: + raise ValueError("Cannot specify both 'id' and 'ids' fields") + return self + + +class Meridian(BaseModel): + type: Literal["Meridian"] = "Meridian" + longitude: float | ValueAndUnit + id: Id | None = None + ids: list[Id] | None = None + + +class ValueAndUnit(BaseModel): + value: float + unit: Unit + + +class BBox(BaseModel): + east_longitude: float + west_longitude: float + south_latitude: float + north_latitude: float + + +class VerticalExtent(BaseModel): + minimum: float + maximum: float + unit: Unit + + +class TemporalExtent(BaseModel): + start: str | float + end: str | float + + +class Usage(BaseModel): + scope: str + area: str + bbox: BBox + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + + +class Axis(BaseModel): + type: Literal["Axis"] = "Axis" + name: str + abbreviation: str + direction: Literal[ + "north", + "northNorthEast", + "northEast", + "eastNorthEast", + "east", + "eastSouthEast", + "southEast", + "southSouthEast", + "south", + "southSouthWest", + "southWest", + "westSouthWest", + "west", + "westNorthWest", + "northWest", + "northNorthWest", + "up", + "down", + "geocentricX", + "geocentricY", + "geocentricZ", + "columnPositive", + "columnNegative", + "rowPositive", + "rowNegative", + "displayRight", + "displayLeft", + "displayUp", + "displayDown", + "forward", + "aft", + "port", + "starboard", + "clockwise", + "counterClockwise", + "towards", + "awayFrom", + "future", + "past", + "unspecified", + ] + meridian: Meridian | None = None + unit: Unit | str | None = None + minimum_value: float | None = None + maximum_value: float | None = None + range_meaning: Literal["exact", "wraparound"] | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class CoordinateSystem(BaseModel): + type: Literal["CoordinateSystem"] = "CoordinateSystem" + name: str | None = None + subtype: Literal[ + "Cartesian", + "spherical", + "ellipsoidal", + "vertical", + "ordinal", + "parametric", + "affine", + "TemporalDateTime", + "TemporalCount", + "TemporalMeasure", + ] + axis: list[Axis] + id: Id | None = None + ids: list[Id] | None = None + + +class Ellipsoid(BaseModel): + type: Literal["Ellipsoid"] = "Ellipsoid" + name: str + semi_major_axis: float | ValueAndUnit | None = None + semi_minor_axis: float | ValueAndUnit | None = None + inverse_flattening: float | None = None + radius: float | ValueAndUnit | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class PrimeMeridian(BaseModel): + type: Literal["PrimeMeridian"] = "PrimeMeridian" + name: str + longitude: float | ValueAndUnit + id: Id | None = None + ids: list[Id] | None = None + + +class GeodeticReferenceFrame(BaseModel): + type: Literal["GeodeticReferenceFrame"] = "GeodeticReferenceFrame" + name: str + anchor: str | None = None + anchor_epoch: float | None = None + ellipsoid: Ellipsoid + prime_meridian: PrimeMeridian | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DynamicGeodeticReferenceFrame(BaseModel): + type: Literal["DynamicGeodeticReferenceFrame"] = "DynamicGeodeticReferenceFrame" + name: str + anchor: str | None = None + anchor_epoch: float | None = None + ellipsoid: Ellipsoid + prime_meridian: PrimeMeridian | None = None + frame_reference_epoch: float + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class VerticalReferenceFrame(BaseModel): + type: Literal["VerticalReferenceFrame"] = "VerticalReferenceFrame" + name: str + anchor: str | None = None + anchor_epoch: float | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DynamicVerticalReferenceFrame(BaseModel): + type: Literal["DynamicVerticalReferenceFrame"] = "DynamicVerticalReferenceFrame" + name: str + anchor: str | None = None + anchor_epoch: float | None = None + frame_reference_epoch: float + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class TemporalDatum(BaseModel): + type: Literal["TemporalDatum"] = "TemporalDatum" + name: str + anchor: str | None = None + calendar: str | None = None + origin: str + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class ParametricDatum(BaseModel): + type: Literal["ParametricDatum"] = "ParametricDatum" + name: str + anchor: str | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class EngineeringDatum(BaseModel): + type: Literal["EngineeringDatum"] = "EngineeringDatum" + name: str + anchor: str | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DatumEnsembleMember(BaseModel): + name: str + id: Id | None = None + ids: list[Id] | None = None + + +class DatumEnsemble(BaseModel): + type: Literal["DatumEnsemble"] = "DatumEnsemble" + name: str + members: list[DatumEnsembleMember] + ellipsoid: Ellipsoid | None = None + accuracy: str + id: Id | None = None + ids: list[Id] | None = None + + +class DeformationModel(BaseModel): + name: str + id: Id | None = None + + +class Method(BaseModel): + type: Literal["OperationMethod"] = "OperationMethod" + name: str + id: Id | None = None + ids: list[Id] | None = None + + +class ParameterValue(BaseModel): + type: Literal["ParameterValue"] = "ParameterValue" + name: str + value: str | float + unit: Unit | str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class Conversion(BaseModel): + type: Literal["Conversion"] = "Conversion" + name: str + method: Method + parameters: list[ParameterValue] | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class GeoidModel(BaseModel): + name: str + interpolation_crs: CRS | None = None + id: Id | None = None + + +Datum = ( + GeodeticReferenceFrame + | DynamicGeodeticReferenceFrame + | VerticalReferenceFrame + | DynamicVerticalReferenceFrame + | TemporalDatum + | ParametricDatum + | EngineeringDatum +) + + +class GeodeticCRS(BaseModel): + type: Literal["GeodeticCRS", "GeographicCRS"] + name: str + datum: GeodeticReferenceFrame | DynamicGeodeticReferenceFrame | None = None + datum_ensemble: DatumEnsemble | None = None + coordinate_system: CoordinateSystem | None = None + deformation_models: list[DeformationModel] | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class ProjectedCRS(BaseModel): + type: Literal["ProjectedCRS"] = "ProjectedCRS" + name: str + base_crs: GeodeticCRS + conversion: Conversion + coordinate_system: CoordinateSystem | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class VerticalCRS(BaseModel): + type: Literal["VerticalCRS"] = "VerticalCRS" + name: str + datum: VerticalReferenceFrame | DynamicVerticalReferenceFrame | None = None + datum_ensemble: DatumEnsemble | None = None + coordinate_system: CoordinateSystem | None = None + geoid_model: GeoidModel | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class TemporalCRS(BaseModel): + type: Literal["TemporalCRS"] = "TemporalCRS" + name: str + datum: TemporalDatum + coordinate_system: CoordinateSystem | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class ParametricCRS(BaseModel): + type: Literal["ParametricCRS"] = "ParametricCRS" + name: str + datum: ParametricDatum + coordinate_system: CoordinateSystem | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class EngineeringCRS(BaseModel): + type: Literal["EngineeringCRS"] = "EngineeringCRS" + name: str + datum: EngineeringDatum + coordinate_system: CoordinateSystem | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DerivedGeodeticCRS(BaseModel): + type: Literal["DerivedGeodeticCRS", "DerivedGeographicCRS"] + name: str + base_crs: GeodeticCRS + conversion: Conversion + coordinate_system: CoordinateSystem + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DerivedProjectedCRS(BaseModel): + type: Literal["DerivedProjectedCRS"] = "DerivedProjectedCRS" + name: str + base_crs: ProjectedCRS + conversion: Conversion + coordinate_system: CoordinateSystem + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DerivedVerticalCRS(BaseModel): + type: Literal["DerivedVerticalCRS"] = "DerivedVerticalCRS" + name: str + base_crs: VerticalCRS + conversion: Conversion + coordinate_system: CoordinateSystem + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DerivedTemporalCRS(BaseModel): + type: Literal["DerivedTemporalCRS"] = "DerivedTemporalCRS" + name: str + base_crs: TemporalCRS + conversion: Conversion + coordinate_system: CoordinateSystem + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DerivedParametricCRS(BaseModel): + type: Literal["DerivedParametricCRS"] = "DerivedParametricCRS" + name: str + base_crs: ParametricCRS + conversion: Conversion + coordinate_system: CoordinateSystem + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class DerivedEngineeringCRS(BaseModel): + type: Literal["DerivedEngineeringCRS"] = "DerivedEngineeringCRS" + name: str + base_crs: EngineeringCRS + conversion: Conversion + coordinate_system: CoordinateSystem + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class CompoundCRS(BaseModel): + type: Literal["CompoundCRS"] = "CompoundCRS" + name: str + components: list[CRS] + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class AbridgedTransformation(BaseModel): + type: Literal["AbridgedTransformation"] = "AbridgedTransformation" + name: str + source_crs: CRS | None = None + method: Method + parameters: list[ParameterValue] + id: Id | None = None + ids: list[Id] | None = None + + +class BoundCRS(BaseModel): + type: Literal["BoundCRS"] = "BoundCRS" + source_crs: CRS + target_crs: CRS + transformation: AbridgedTransformation + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +CRS = ( + BoundCRS + | CompoundCRS + | DerivedEngineeringCRS + | DerivedGeodeticCRS + | DerivedParametricCRS + | DerivedProjectedCRS + | DerivedTemporalCRS + | DerivedVerticalCRS + | EngineeringCRS + | GeodeticCRS + | ParametricCRS + | ProjectedCRS + | TemporalCRS + | VerticalCRS +) + + +class SingleOperation(BaseModel): + type: Literal["Transformation", "Conversion"] + name: str + source_crs: CRS | None = None + target_crs: CRS | None = None + method: Method + parameters: list[ParameterValue] | None = None + accuracy: str | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class ConcatenatedOperation(BaseModel): + type: Literal["ConcatenatedOperation"] = "ConcatenatedOperation" + name: str + source_crs: CRS + target_crs: CRS + steps: list[SingleOperation] + accuracy: str | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +class CoordinateMetadata(BaseModel): + type: Literal["CoordinateMetadata"] = "CoordinateMetadata" + crs: CRS + coordinateEpoch: float | None = None + + +class PointMotionOperation(BaseModel): + type: Literal["PointMotionOperation"] = "PointMotionOperation" + name: str + source_crs: CRS + method: Method + parameters: list[ParameterValue] + accuracy: str | None = None + scope: str | None = None + area: str | None = None + bbox: BBox | None = None + vertical_extent: VerticalExtent | None = None + temporal_extent: TemporalExtent | None = None + usages: list[Usage] | None = None + remarks: str | None = None + id: Id | None = None + ids: list[Id] | None = None + + +ProjJSON = ( + CRS + | Datum + | DatumEnsemble + | Ellipsoid + | PrimeMeridian + | SingleOperation + | ConcatenatedOperation + | CoordinateMetadata +) + + +# Update forward references +GeoidModel.model_rebuild() +CompoundCRS.model_rebuild() +AbridgedTransformation.model_rebuild() +BoundCRS.model_rebuild() diff --git a/tests/test_data_api/conftest.py b/tests/test_data_api/conftest.py index 486a4ca..2e204fd 100644 --- a/tests/test_data_api/conftest.py +++ b/tests/test_data_api/conftest.py @@ -1,14153 +1,97 @@ from __future__ import annotations +import json +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest from zarr import open_group from zarr.core.buffer import default_buffer_prototype -example_zarr_json = r"""{ - "attributes": {}, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": { - "conditions": { - "attributes": {}, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/meteorology": { - "attributes": {}, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/meteorology/cams": { - "attributes": { - "Conventions": "CF-1.7", - "GRIB_centre": "ecmf", - "GRIB_centreDescription": "European Centre for Medium-Range Weather Forecasts", - "GRIB_edition": 1, - "GRIB_subCentre": 0, - "history": "2025-02-27T07:57 GRIB to CDM+CF via cfgrib-0.9.10.4/ecCodes-2.34.1 with {\"source\": \"tmp/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.SAFE/GRANULE/L1C_T32TLQ_A041032_20250113T103310/AUX_DATA/AUX_CAMSFO\", \"filter_by_keys\": {}, \"encode_cf\": [\"parameter\", \"time\", \"geography\", \"vertical\"]}", - "institution": "European Centre for Medium-Range Weather Forecasts" - }, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/meteorology/cams/surface": { - "shape": [], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "original GRIB coordinate for key: level(surface)", - "units": "1", - "_FillValue": "AAAAAAAA+H8=" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/aod865": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "aod865", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total Aerosol Optical Depth at 865nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210215, - "GRIB_shortName": "aod865", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Total Aerosol Optical Depth at 865nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/latitude": { - "shape": [ - 9 - ], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "latitude", - "standard_name": "latitude", - "stored_direction": "decreasing", - "units": "degrees_north", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/number": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "ensemble member numerical id", - "standard_name": "realization", - "units": "1" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/z": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "geopotential", - "GRIB_cfVarName": "z", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Geopotential", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 129, - "GRIB_shortName": "z", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "m**2 s**-2", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "m**2 s**-2" - }, - "long_name": "Geopotential", - "standard_name": "geopotential", - "units": "m**2 s**-2", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/step": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "time since forecast_reference_time", - "standard_name": "forecast_period", - "dtype": "timedelta64[ns]", - "units": "minutes" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/omaod550": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "omaod550", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Organic Matter Aerosol Optical Depth at 550nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210210, - "GRIB_shortName": "omaod550", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Organic Matter Aerosol Optical Depth at 550nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/aod469": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "aod469", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total Aerosol Optical Depth at 469nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210213, - "GRIB_shortName": "aod469", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Total Aerosol Optical Depth at 469nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/aod670": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "aod670", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total Aerosol Optical Depth at 670nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210214, - "GRIB_shortName": "aod670", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Total Aerosol Optical Depth at 670nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/isobaricInhPa": { - "shape": [], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "pressure", - "positive": "down", - "standard_name": "air_pressure", - "stored_direction": "decreasing", - "units": "hPa", - "_FillValue": "AAAAAAAA+H8=" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/duaod550": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "duaod550", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Dust Aerosol Optical Depth at 550nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210209, - "GRIB_shortName": "duaod550", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "isobaricInhPa", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Dust Aerosol Optical Depth at 550nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/ssaod550": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "ssaod550", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Sea Salt Aerosol Optical Depth at 550nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210208, - "GRIB_shortName": "ssaod550", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Sea Salt Aerosol Optical Depth at 550nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/time": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "_eopf_attrs": { - "_eopf_decode_datetime64": "datetime64[ns]" - }, - "long_name": "initial time of forecast", - "standard_name": "forecast_reference_time", - "units": "days since 2025-01-13 00:00:00", - "calendar": "proleptic_gregorian" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/valid_time": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "_eopf_attrs": { - "_eopf_decode_datetime64": "datetime64[ns]" - }, - "long_name": "time", - "standard_name": "time", - "units": "days since 2025-01-13 10:33:00", - "calendar": "proleptic_gregorian" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/bcaod550": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "bcaod550", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Black Carbon Aerosol Optical Depth at 550nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210211, - "GRIB_shortName": "bcaod550", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Black Carbon Aerosol Optical Depth at 550nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/aod550": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "aod550", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total Aerosol Optical Depth at 550nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210207, - "GRIB_shortName": "aod550", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Total Aerosol Optical Depth at 550nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/longitude": { - "shape": [ - 9 - ], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "longitude", - "standard_name": "longitude", - "units": "degrees_east", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/aod1240": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "aod1240", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total Aerosol Optical Depth at 1240nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210216, - "GRIB_shortName": "aod1240", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Total Aerosol Optical Depth at 1240nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/cams/suaod550": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "suaod550", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Sulphate Aerosol Optical Depth at 550nm", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 210212, - "GRIB_shortName": "suaod550", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "~", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "~" - }, - "long_name": "Sulphate Aerosol Optical Depth at 550nm", - "standard_name": "unknown", - "units": "~", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf": { - "attributes": { - "Conventions": "CF-1.7", - "GRIB_centre": "ecmf", - "GRIB_centreDescription": "European Centre for Medium-Range Weather Forecasts", - "GRIB_edition": 1, - "GRIB_subCentre": 0, - "history": "2025-02-27T07:57 GRIB to CDM+CF via cfgrib-0.9.10.4/ecCodes-2.34.1 with {\"source\": \"tmp/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.SAFE/GRANULE/L1C_T32TLQ_A041032_20250113T103310/AUX_DATA/AUX_ECMWFT\", \"filter_by_keys\": {}, \"encode_cf\": [\"parameter\", \"time\", \"geography\", \"vertical\"]}", - "institution": "European Centre for Medium-Range Weather Forecasts" - }, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/meteorology/ecmwf/surface": { - "shape": [], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "original GRIB coordinate for key: level(surface)", - "units": "1", - "_FillValue": "AAAAAAAA+H8=" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/v10": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "v10", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "10 metre V wind component", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 166, - "GRIB_shortName": "10v", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "m s**-1", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "m s**-1" - }, - "long_name": "10 metre V wind component", - "standard_name": "unknown", - "units": "m s**-1", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/latitude": { - "shape": [ - 9 - ], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "latitude", - "standard_name": "latitude", - "stored_direction": "decreasing", - "units": "degrees_north", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/number": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "ensemble member numerical id", - "standard_name": "realization", - "units": "1" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/step": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "time since forecast_reference_time", - "standard_name": "forecast_period", - "dtype": "timedelta64[ns]", - "units": "minutes" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/r": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "relative_humidity", - "GRIB_cfVarName": "r", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Relative humidity", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 157, - "GRIB_shortName": "r", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "isobaricInhPa", - "GRIB_units": "%", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "%" - }, - "long_name": "Relative humidity", - "standard_name": "relative_humidity", - "units": "%", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/isobaricInhPa": { - "shape": [], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "pressure", - "positive": "down", - "standard_name": "air_pressure", - "stored_direction": "decreasing", - "units": "hPa", - "_FillValue": "AAAAAAAA+H8=" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/tcwv": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "lwe_thickness_of_atmosphere_mass_content_of_water_vapor", - "GRIB_cfVarName": "tcwv", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total column vertically-integrated water vapour", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 137, - "GRIB_shortName": "tcwv", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "kg m**-2", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "kg m**-2" - }, - "long_name": "Total column vertically-integrated water vapour", - "standard_name": "lwe_thickness_of_atmosphere_mass_content_of_water_vapor", - "units": "kg m**-2", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/u10": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "unknown", - "GRIB_cfVarName": "u10", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "10 metre U wind component", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 165, - "GRIB_shortName": "10u", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "m s**-1", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "m s**-1" - }, - "long_name": "10 metre U wind component", - "standard_name": "unknown", - "units": "m s**-1", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/time": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "_eopf_attrs": { - "_eopf_decode_datetime64": "datetime64[ns]" - }, - "long_name": "initial time of forecast", - "standard_name": "forecast_reference_time", - "units": "days since 2025-01-13 00:00:00", - "calendar": "proleptic_gregorian" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/valid_time": { - "shape": [], - "data_type": "int64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "_eopf_attrs": { - "_eopf_decode_datetime64": "datetime64[ns]" - }, - "long_name": "time", - "standard_name": "time", - "units": "days since 2025-01-13 10:33:00", - "calendar": "proleptic_gregorian" - }, - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/tco3": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "atmosphere_mass_content_of_ozone", - "GRIB_cfVarName": "tco3", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Total column ozone", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 206, - "GRIB_shortName": "tco3", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "kg m**-2", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "kg m**-2" - }, - "long_name": "Total column ozone", - "standard_name": "atmosphere_mass_content_of_ozone", - "units": "kg m**-2", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/msl": { - "shape": [ - 9, - 9 - ], - "data_type": "float32", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9, - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - }, - { - "name": "blosc", - "configuration": { - "typesize": 4, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "GRIB_NV": 0, - "GRIB_Nx": 9, - "GRIB_Ny": 9, - "GRIB_cfName": "air_pressure_at_mean_sea_level", - "GRIB_cfVarName": "msl", - "GRIB_dataType": "fc", - "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", - "GRIB_gridType": "regular_ll", - "GRIB_iDirectionIncrementInDegrees": 0.177, - "GRIB_iScansNegatively": 0, - "GRIB_jDirectionIncrementInDegrees": 0.121, - "GRIB_jPointsAreConsecutive": 0, - "GRIB_jScansPositively": 0, - "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, - "GRIB_latitudeOfLastGridPointInDegrees": 44.16, - "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, - "GRIB_longitudeOfLastGridPointInDegrees": 7.872, - "GRIB_missingValue": 3.4028234663852886e+38, - "GRIB_name": "Mean sea level pressure", - "GRIB_numberOfPoints": 81, - "GRIB_paramId": 151, - "GRIB_shortName": "msl", - "GRIB_stepType": "instant", - "GRIB_stepUnits": 0, - "GRIB_totalNumber": 0, - "GRIB_typeOfLevel": "surface", - "GRIB_units": "Pa", - "_eopf_attrs": { - "coordinates": [ - "number", - "time", - "step", - "surface", - "latitude", - "longitude", - "valid_time", - "isobaricInhPa" - ], - "dimensions": [ - "latitude", - "longitude" - ], - "units": "Pa" - }, - "long_name": "Mean sea level pressure", - "standard_name": "air_pressure_at_mean_sea_level", - "units": "Pa", - "coordinates": "isobaricInhPa number step surface time valid_time", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "latitude", - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/meteorology/ecmwf/longitude": { - "shape": [ - 9 - ], - "data_type": "float64", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 9 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0.0, - "codecs": [ - { - "name": "bytes", - "configuration": { - "endian": "little" - } - } - ], - "attributes": { - "long_name": "longitude", - "standard_name": "longitude", - "units": "degrees_east", - "_FillValue": "AAAAAAAA+H8=" - }, - "dimension_names": [ - "longitude" - ], - "zarr_format": 3, - "node_type": "array", - "storage_transformers": [] - }, - "conditions/mask": { - "attributes": {}, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/mask/l1c_classification": { - "attributes": {}, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/mask/l1c_classification/r60m": { - "attributes": {}, - "zarr_format": 3, - "consolidated_metadata": { - "kind": "inline", - "must_understand": false, - "metadata": {} - }, - "node_type": "group" - }, - "conditions/mask/l1c_classification/r60m/b00": { - "shape": [ - 1830, - 1830 - ], - "data_type": "uint8", - "chunk_grid": { - "name": "regular", - "configuration": { - "chunk_shape": [ - 1830, - 1830 - ] - } - }, - "chunk_key_encoding": { - "name": "default", - "configuration": { - "separator": "/" - } - }, - "fill_value": 0, - "codecs": [ - { - "name": "bytes" - }, - { - "name": "blosc", - "configuration": { - "typesize": 1, - "cname": "zstd", - "clevel": 3, - "shuffle": "shuffle", - "blocksize": 0 - } - } - ], - "attributes": { - "_eopf_attrs": { - "coordinates": [ - "x", - "y" - ], - "dimensions": [ - "y", - "x" - ], - "flag_masks": [ - 1, - 2, - 4 - ], - "flag_meanings": [ - "OPAQUE", - "CIRRUS", - "SNOW_ICE" - ] - }, - "dtype": " Group: + meta = _load_geozarr_file("sentinel_2.json") + example_group = open_group( + store={ + "zarr.json": default_buffer_prototype().buffer.from_bytes( + json.dumps(meta).encode("utf-8") + ) + }, + mode="r", + ) + return example_group + + +def _load_geozarr_file(filename: str) -> dict[str, Any]: + """Load an example Geozarr group metadata file from the geozarr_examples directory.""" + examples_dir = Path(__file__).parent / "geozarr_examples" + file_path = examples_dir / filename + with open(file_path, "r") as f: + return json.load(f) + + +def _load_projjson_file(filename: str) -> dict[str, Any]: + """Load a PROJ JSON file from the projjson_examples directory.""" + examples_dir = Path(__file__).parent / "projjson_examples" + file_path = examples_dir / filename + with open(file_path, "r") as f: + return json.load(f) + + +@pytest.fixture +def projected_crs_json() -> dict[str, Any]: + """Load projected CRS example.""" + return _load_projjson_file("projected_crs.json") + + +@pytest.fixture +def bound_crs_json() -> dict[str, Any]: + """Load bound CRS example.""" + return _load_projjson_file("bound_crs.json") + + +@pytest.fixture +def compound_crs_json() -> dict[str, Any]: + """Load compound CRS example.""" + return _load_projjson_file("compound_crs.json") + + +@pytest.fixture +def transformation_json() -> dict[str, Any]: + """Load transformation example.""" + return _load_projjson_file("transformation.json") + + +@pytest.fixture +def datum_ensemble_json() -> dict[str, Any]: + """Load datum ensemble example.""" + return _load_projjson_file("datum_ensemble.json") + + +@pytest.fixture +def explicit_prime_meridian_json() -> dict[str, Any]: + """Load explicit prime meridian example.""" + return _load_projjson_file("explicit_prime_meridian.json") + + +@pytest.fixture +def implicit_prime_meridian_json() -> dict[str, Any]: + """Load implicit prime meridian example.""" + return _load_projjson_file("implicit_prime_meridian.json") + + +@pytest.fixture +def all_projjson_examples() -> list[dict[str, Any]]: + """Load all PROJ JSON examples.""" + examples_dir = Path(__file__).parent / "projjson_examples" + examples = [] + for json_file in examples_dir.glob("*.json"): + with open(json_file, "r") as f: + examples.append(json.load(f)) + return examples diff --git a/tests/test_data_api/geozarr_examples/sentinel_2.json b/tests/test_data_api/geozarr_examples/sentinel_2.json new file mode 100644 index 0000000..5a8faa5 --- /dev/null +++ b/tests/test_data_api/geozarr_examples/sentinel_2.json @@ -0,0 +1,14142 @@ +{ + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": { + "conditions": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/meteorology": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/meteorology/cams": { + "attributes": { + "Conventions": "CF-1.7", + "GRIB_centre": "ecmf", + "GRIB_centreDescription": "European Centre for Medium-Range Weather Forecasts", + "GRIB_edition": 1, + "GRIB_subCentre": 0, + "history": "2025-02-27T07:57 GRIB to CDM+CF via cfgrib-0.9.10.4/ecCodes-2.34.1 with {\"source\": \"tmp/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.SAFE/GRANULE/L1C_T32TLQ_A041032_20250113T103310/AUX_DATA/AUX_CAMSFO\", \"filter_by_keys\": {}, \"encode_cf\": [\"parameter\", \"time\", \"geography\", \"vertical\"]}", + "institution": "European Centre for Medium-Range Weather Forecasts" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/meteorology/cams/surface": { + "shape": [], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "original GRIB coordinate for key: level(surface)", + "units": "1", + "_FillValue": "AAAAAAAA+H8=" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/aod865": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "aod865", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total Aerosol Optical Depth at 865nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210215, + "GRIB_shortName": "aod865", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Total Aerosol Optical Depth at 865nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/latitude": { + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "latitude", + "standard_name": "latitude", + "stored_direction": "decreasing", + "units": "degrees_north", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/number": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "ensemble member numerical id", + "standard_name": "realization", + "units": "1" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/z": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "geopotential", + "GRIB_cfVarName": "z", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Geopotential", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 129, + "GRIB_shortName": "z", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "m**2 s**-2", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "m**2 s**-2" + }, + "long_name": "Geopotential", + "standard_name": "geopotential", + "units": "m**2 s**-2", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/step": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "time since forecast_reference_time", + "standard_name": "forecast_period", + "dtype": "timedelta64[ns]", + "units": "minutes" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/omaod550": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "omaod550", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Organic Matter Aerosol Optical Depth at 550nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210210, + "GRIB_shortName": "omaod550", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Organic Matter Aerosol Optical Depth at 550nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/aod469": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "aod469", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total Aerosol Optical Depth at 469nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210213, + "GRIB_shortName": "aod469", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Total Aerosol Optical Depth at 469nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/aod670": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "aod670", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total Aerosol Optical Depth at 670nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210214, + "GRIB_shortName": "aod670", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Total Aerosol Optical Depth at 670nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/isobaricInhPa": { + "shape": [], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "pressure", + "positive": "down", + "standard_name": "air_pressure", + "stored_direction": "decreasing", + "units": "hPa", + "_FillValue": "AAAAAAAA+H8=" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/duaod550": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "duaod550", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Dust Aerosol Optical Depth at 550nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210209, + "GRIB_shortName": "duaod550", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "isobaricInhPa", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Dust Aerosol Optical Depth at 550nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/ssaod550": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "ssaod550", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Sea Salt Aerosol Optical Depth at 550nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210208, + "GRIB_shortName": "ssaod550", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Sea Salt Aerosol Optical Depth at 550nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/time": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "_eopf_attrs": { + "_eopf_decode_datetime64": "datetime64[ns]" + }, + "long_name": "initial time of forecast", + "standard_name": "forecast_reference_time", + "units": "days since 2025-01-13 00:00:00", + "calendar": "proleptic_gregorian" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/valid_time": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "_eopf_attrs": { + "_eopf_decode_datetime64": "datetime64[ns]" + }, + "long_name": "time", + "standard_name": "time", + "units": "days since 2025-01-13 10:33:00", + "calendar": "proleptic_gregorian" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/bcaod550": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "bcaod550", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Black Carbon Aerosol Optical Depth at 550nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210211, + "GRIB_shortName": "bcaod550", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Black Carbon Aerosol Optical Depth at 550nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/aod550": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "aod550", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total Aerosol Optical Depth at 550nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210207, + "GRIB_shortName": "aod550", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Total Aerosol Optical Depth at 550nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/longitude": { + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "longitude", + "standard_name": "longitude", + "units": "degrees_east", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/aod1240": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "aod1240", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total Aerosol Optical Depth at 1240nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210216, + "GRIB_shortName": "aod1240", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Total Aerosol Optical Depth at 1240nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/cams/suaod550": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "suaod550", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Sulphate Aerosol Optical Depth at 550nm", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 210212, + "GRIB_shortName": "suaod550", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "~", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "~" + }, + "long_name": "Sulphate Aerosol Optical Depth at 550nm", + "standard_name": "unknown", + "units": "~", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf": { + "attributes": { + "Conventions": "CF-1.7", + "GRIB_centre": "ecmf", + "GRIB_centreDescription": "European Centre for Medium-Range Weather Forecasts", + "GRIB_edition": 1, + "GRIB_subCentre": 0, + "history": "2025-02-27T07:57 GRIB to CDM+CF via cfgrib-0.9.10.4/ecCodes-2.34.1 with {\"source\": \"tmp/S2B_MSIL1C_20250113T103309_N0511_R108_T32TLQ_20250113T122458.SAFE/GRANULE/L1C_T32TLQ_A041032_20250113T103310/AUX_DATA/AUX_ECMWFT\", \"filter_by_keys\": {}, \"encode_cf\": [\"parameter\", \"time\", \"geography\", \"vertical\"]}", + "institution": "European Centre for Medium-Range Weather Forecasts" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/meteorology/ecmwf/surface": { + "shape": [], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "original GRIB coordinate for key: level(surface)", + "units": "1", + "_FillValue": "AAAAAAAA+H8=" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/v10": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "v10", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "10 metre V wind component", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 166, + "GRIB_shortName": "10v", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "m s**-1", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "m s**-1" + }, + "long_name": "10 metre V wind component", + "standard_name": "unknown", + "units": "m s**-1", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/latitude": { + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "latitude", + "standard_name": "latitude", + "stored_direction": "decreasing", + "units": "degrees_north", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/number": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "ensemble member numerical id", + "standard_name": "realization", + "units": "1" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/step": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "time since forecast_reference_time", + "standard_name": "forecast_period", + "dtype": "timedelta64[ns]", + "units": "minutes" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/r": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "relative_humidity", + "GRIB_cfVarName": "r", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Relative humidity", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 157, + "GRIB_shortName": "r", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "isobaricInhPa", + "GRIB_units": "%", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "%" + }, + "long_name": "Relative humidity", + "standard_name": "relative_humidity", + "units": "%", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/isobaricInhPa": { + "shape": [], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "pressure", + "positive": "down", + "standard_name": "air_pressure", + "stored_direction": "decreasing", + "units": "hPa", + "_FillValue": "AAAAAAAA+H8=" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/tcwv": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "lwe_thickness_of_atmosphere_mass_content_of_water_vapor", + "GRIB_cfVarName": "tcwv", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total column vertically-integrated water vapour", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 137, + "GRIB_shortName": "tcwv", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "kg m**-2", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "kg m**-2" + }, + "long_name": "Total column vertically-integrated water vapour", + "standard_name": "lwe_thickness_of_atmosphere_mass_content_of_water_vapor", + "units": "kg m**-2", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/u10": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "unknown", + "GRIB_cfVarName": "u10", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "10 metre U wind component", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 165, + "GRIB_shortName": "10u", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "m s**-1", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "m s**-1" + }, + "long_name": "10 metre U wind component", + "standard_name": "unknown", + "units": "m s**-1", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/time": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "_eopf_attrs": { + "_eopf_decode_datetime64": "datetime64[ns]" + }, + "long_name": "initial time of forecast", + "standard_name": "forecast_reference_time", + "units": "days since 2025-01-13 00:00:00", + "calendar": "proleptic_gregorian" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/valid_time": { + "shape": [], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "_eopf_attrs": { + "_eopf_decode_datetime64": "datetime64[ns]" + }, + "long_name": "time", + "standard_name": "time", + "units": "days since 2025-01-13 10:33:00", + "calendar": "proleptic_gregorian" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/tco3": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "atmosphere_mass_content_of_ozone", + "GRIB_cfVarName": "tco3", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Total column ozone", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 206, + "GRIB_shortName": "tco3", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "kg m**-2", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "kg m**-2" + }, + "long_name": "Total column ozone", + "standard_name": "atmosphere_mass_content_of_ozone", + "units": "kg m**-2", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/msl": { + "shape": [ + 9, + 9 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9, + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "blosc", + "configuration": { + "typesize": 4, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "GRIB_NV": 0, + "GRIB_Nx": 9, + "GRIB_Ny": 9, + "GRIB_cfName": "air_pressure_at_mean_sea_level", + "GRIB_cfVarName": "msl", + "GRIB_dataType": "fc", + "GRIB_gridDefinitionDescription": "Latitude/Longitude Grid", + "GRIB_gridType": "regular_ll", + "GRIB_iDirectionIncrementInDegrees": 0.177, + "GRIB_iScansNegatively": 0, + "GRIB_jDirectionIncrementInDegrees": 0.121, + "GRIB_jPointsAreConsecutive": 0, + "GRIB_jScansPositively": 0, + "GRIB_latitudeOfFirstGridPointInDegrees": 45.126, + "GRIB_latitudeOfLastGridPointInDegrees": 44.16, + "GRIB_longitudeOfFirstGridPointInDegrees": 6.457, + "GRIB_longitudeOfLastGridPointInDegrees": 7.872, + "GRIB_missingValue": 3.4028234663852886e+38, + "GRIB_name": "Mean sea level pressure", + "GRIB_numberOfPoints": 81, + "GRIB_paramId": 151, + "GRIB_shortName": "msl", + "GRIB_stepType": "instant", + "GRIB_stepUnits": 0, + "GRIB_totalNumber": 0, + "GRIB_typeOfLevel": "surface", + "GRIB_units": "Pa", + "_eopf_attrs": { + "coordinates": [ + "number", + "time", + "step", + "surface", + "latitude", + "longitude", + "valid_time", + "isobaricInhPa" + ], + "dimensions": [ + "latitude", + "longitude" + ], + "units": "Pa" + }, + "long_name": "Mean sea level pressure", + "standard_name": "air_pressure_at_mean_sea_level", + "units": "Pa", + "coordinates": "isobaricInhPa number step surface time valid_time", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "latitude", + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/meteorology/ecmwf/longitude": { + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + } + ], + "attributes": { + "long_name": "longitude", + "standard_name": "longitude", + "units": "degrees_east", + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "longitude" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "conditions/mask": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/mask/l1c_classification": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/mask/l1c_classification/r60m": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "conditions/mask/l1c_classification/r60m/b00": { + "shape": [ + 1830, + 1830 + ], + "data_type": "uint8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1830, + 1830 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "blosc", + "configuration": { + "typesize": 1, + "cname": "zstd", + "clevel": 3, + "shuffle": "shuffle", + "blocksize": 0 + } + } + ], + "attributes": { + "_eopf_attrs": { + "coordinates": [ + "x", + "y" + ], + "dimensions": [ + "y", + "x" + ], + "flag_masks": [ + 1, + 2, + 4 + ], + "flag_meanings": [ + "OPAQUE", + "CIRRUS", + "SNOW_ICE" + ] + }, + "dtype": " None: check_standard_name("invalid_standard_name") -def test_multiscales_round_trip() -> None: +def test_multiscales_round_trip(example_group) -> None: """ Ensure that we can round-trip multiscale metadata through the `Multiscales` model. """ diff --git a/tests/test_data_api/test_projjson.py b/tests/test_data_api/test_projjson.py new file mode 100644 index 0000000..fb37b71 --- /dev/null +++ b/tests/test_data_api/test_projjson.py @@ -0,0 +1,789 @@ +""" +Tests for PROJ JSON Pydantic models + +These tests validate the Pydantic models against various PROJ JSON examples +and ensure proper validation and serialization. +""" + +from __future__ import annotations + +from typing import Any + +import pytest +from pydantic import ValidationError + +from eopf_geozarr.data_api.geozarr.projjson import ( + Axis, + BBox, + CompoundCRS, + CoordinateMetadata, + CoordinateSystem, + DatumEnsemble, + Ellipsoid, + GeodeticCRS, + GeodeticReferenceFrame, + Id, + ProjectedCRS, + SingleOperation, + Unit, +) + + +class TestBasicModels: + """Test basic building block models""" + + def test_id_model(self) -> None: + """Test Id model validation""" + # Valid ID with required fields + id_data: dict[str, Any] = {"authority": "EPSG", "code": 4326} + id_obj: Id = Id(**id_data) + assert id_obj.authority == "EPSG" + assert id_obj.code == 4326 + + # ID with string code + id_data_str: dict[str, Any] = {"authority": "EPSG", "code": "4326"} + id_obj_str: Id = Id(**id_data_str) + assert id_obj_str.code == "4326" + + # ID with optional fields + id_full: dict[str, Any] = { + "authority": "EPSG", + "code": 4326, + "version": "10.095", + "authority_citation": "EPSG Geodetic Parameter Dataset", + "uri": "urn:ogc:def:crs:EPSG::4326", + } + id_obj_full: Id = Id(**id_full) + assert id_obj_full.version == "10.095" + assert id_obj_full.uri == "urn:ogc:def:crs:EPSG::4326" + + # Missing required field should raise ValidationError + with pytest.raises(ValidationError): + Id(authority="EPSG") # missing code + + def test_unit_model(self) -> None: + """Test Unit model validation""" + unit_data: dict[str, Any] = { + "type": "Unit", + "name": "metre", + "conversion_factor": 1.0, + } + unit: Unit = Unit(**unit_data) + assert unit.name == "metre" + assert unit.conversion_factor == 1.0 + + # With ID + unit_with_id: dict[str, Any] = { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + "id": {"authority": "EPSG", "code": 9122}, + } + unit = Unit(**unit_with_id) + assert unit.id.authority == "EPSG" + assert unit.id.code == 9122 + + def test_bbox_model(self) -> None: + """Test BBox model validation""" + bbox_data: dict[str, float] = { + "east_longitude": 180.0, + "west_longitude": -180.0, + "south_latitude": -90.0, + "north_latitude": 90.0, + } + bbox: BBox = BBox(**bbox_data) + assert bbox.east_longitude == 180.0 + assert bbox.north_latitude == 90.0 + + # Missing required field + with pytest.raises(ValidationError): + BBox(east_longitude=180.0, west_longitude=-180.0) # missing latitude fields + + def test_axis_model(self) -> None: + """Test Axis model validation""" + axis_data: dict[str, str] = { + "type": "Axis", + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + } + axis: Axis = Axis(**axis_data) + assert axis.name == "Geodetic latitude" + assert axis.direction == "north" + + # Invalid direction should raise ValidationError + with pytest.raises(ValidationError): + Axis( + type="Axis", + name="Invalid", + abbreviation="Inv", + direction="invalid_direction", + ) + + +class TestEllipsoidModel: + """Test Ellipsoid model variations""" + + def test_ellipsoid_with_semi_axes(self) -> None: + """Test ellipsoid with semi-major and semi-minor axes""" + ellipsoid_data: dict[str, Any] = { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "semi_minor_axis": 6356752.314245179, + } + ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data) + assert ellipsoid.name == "WGS 84" + assert ellipsoid.semi_major_axis == 6378137.0 + + def test_ellipsoid_with_inverse_flattening(self) -> None: + """Test ellipsoid with inverse flattening""" + ellipsoid_data: dict[str, Any] = { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + } + ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data) + assert ellipsoid.inverse_flattening == 298.257223563 + + def test_ellipsoid_sphere(self) -> None: + """Test spherical ellipsoid (equal radii)""" + ellipsoid_data: dict[str, Any] = { + "type": "Ellipsoid", + "name": "Sphere", + "radius": 6371000.0, + } + ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data) + assert ellipsoid.radius == 6371000.0 + + +class TestCoordinateSystemModel: + """Test CoordinateSystem model""" + + def test_ellipsoidal_coordinate_system(self) -> None: + """Test ellipsoidal coordinate system""" + cs_data: dict[str, Any] = { + "type": "CoordinateSystem", + "subtype": "ellipsoidal", + "axis": [ + { + "type": "Axis", + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + }, + }, + { + "type": "Axis", + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + }, + }, + ], + } + cs: CoordinateSystem = CoordinateSystem(**cs_data) + assert cs.subtype == "ellipsoidal" + assert len(cs.axis) == 2 + assert cs.axis[0].name == "Geodetic latitude" + + def test_cartesian_coordinate_system(self) -> None: + """Test Cartesian coordinate system""" + cs_data: dict[str, Any] = { + "type": "CoordinateSystem", + "subtype": "Cartesian", + "axis": [ + { + "type": "Axis", + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": {"type": "Unit", "name": "metre", "conversion_factor": 1.0}, + }, + { + "type": "Axis", + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": {"type": "Unit", "name": "metre", "conversion_factor": 1.0}, + }, + ], + } + cs: CoordinateSystem = CoordinateSystem(**cs_data) + assert cs.subtype == "Cartesian" + assert cs.axis[0].direction == "east" + assert cs.axis[1].direction == "north" + + +class TestCRSModels: + """Test various CRS model types""" + + def test_geodetic_crs_wgs84(self) -> None: + """Test WGS 84 geodetic CRS""" + wgs84_data: dict[str, Any] = { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + }, + }, + "coordinate_system": { + "type": "CoordinateSystem", + "subtype": "ellipsoidal", + "axis": [ + { + "type": "Axis", + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + }, + }, + { + "type": "Axis", + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + }, + }, + ], + }, + "id": {"authority": "EPSG", "code": 4326}, + } + crs: GeodeticCRS = GeodeticCRS(**wgs84_data) + assert crs.name == "WGS 84" + assert crs.datum.name == "World Geodetic System 1984" + assert crs.id.code == 4326 + + def test_projected_crs_utm(self) -> None: + """Test UTM projected CRS""" + utm_data: dict[str, Any] = { + "type": "ProjectedCRS", + "name": "WGS 84 / UTM zone 33N", + "base_crs": { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + }, + }, + }, + "conversion": { + "type": "Conversion", + "name": "UTM zone 33N", + "method": {"type": "OperationMethod", "name": "Transverse Mercator"}, + "parameters": [ + { + "type": "ParameterValue", + "name": "Latitude of natural origin", + "value": 0.0, + "unit": { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + }, + }, + { + "type": "ParameterValue", + "name": "Longitude of natural origin", + "value": 15.0, + "unit": { + "type": "Unit", + "name": "degree", + "conversion_factor": 0.017453292519943295, + }, + }, + ], + }, + "coordinate_system": { + "type": "CoordinateSystem", + "subtype": "Cartesian", + "axis": [ + { + "type": "Axis", + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": { + "type": "Unit", + "name": "metre", + "conversion_factor": 1.0, + }, + }, + { + "type": "Axis", + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": { + "type": "Unit", + "name": "metre", + "conversion_factor": 1.0, + }, + }, + ], + }, + } + crs: ProjectedCRS = ProjectedCRS(**utm_data) + assert crs.name == "WGS 84 / UTM zone 33N" + assert crs.base_crs.name == "WGS 84" + assert crs.conversion.name == "UTM zone 33N" + + def test_compound_crs(self) -> None: + """Test compound CRS with horizontal and vertical components""" + compound_data: dict[str, Any] = { + "type": "CompoundCRS", + "name": "WGS 84 + EGM96 height", + "components": [ + { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + }, + }, + }, + { + "type": "VerticalCRS", + "name": "EGM96 height", + "datum": {"type": "VerticalReferenceFrame", "name": "EGM96 geoid"}, + }, + ], + } + crs: CompoundCRS = CompoundCRS(**compound_data) + assert crs.name == "WGS 84 + EGM96 height" + assert len(crs.components) == 2 + assert crs.components[0].name == "WGS 84" + assert crs.components[1].name == "EGM96 height" + + +class TestDatumEnsemble: + """Test DatumEnsemble model""" + + def test_datum_ensemble_creation(self) -> None: + """Test creation of datum ensemble""" + ensemble_data: dict[str, Any] = { + "type": "DatumEnsemble", + "name": "World Geodetic System 1984 ensemble", + "members": [ + {"name": "World Geodetic System 1984 (Transit)"}, + {"name": "World Geodetic System 1984 (G730)"}, + {"name": "World Geodetic System 1984 (G873)"}, + ], + "ellipsoid": { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + }, + "accuracy": "2.0", + } + ensemble: DatumEnsemble = DatumEnsemble(**ensemble_data) + assert ensemble.name == "World Geodetic System 1984 ensemble" + assert len(ensemble.members) == 3 + assert ensemble.accuracy == "2.0" + + +class TestOperations: + """Test operation models""" + + def test_coordinate_metadata(self) -> None: + """Test coordinate metadata""" + metadata_data: dict[str, Any] = { + "type": "CoordinateMetadata", + "crs": { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + }, + }, + }, + "coordinateEpoch": 2020.0, + } + metadata: CoordinateMetadata = CoordinateMetadata(**metadata_data) + assert metadata.coordinateEpoch == 2020.0 + assert metadata.crs.name == "WGS 84" + + def test_single_operation(self) -> None: + """Test single operation (transformation)""" + operation_data: dict[str, Any] = { + "type": "Transformation", + "name": "NAD27 to NAD83 (1)", + "method": {"type": "OperationMethod", "name": "NADCON"}, + "parameters": [ + { + "type": "ParameterValue", + "name": "Latitude difference file", + "value": "conus.las", + }, + { + "type": "ParameterValue", + "name": "Longitude difference file", + "value": "conus.los", + }, + ], + "accuracy": "0.15", + } + operation: SingleOperation = SingleOperation(**operation_data) + assert operation.name == "NAD27 to NAD83 (1)" + assert operation.accuracy == "0.15" + assert len(operation.parameters) == 2 + + +class TestValidationEdgeCases: + """Test validation edge cases and error handling""" + + def test_invalid_crs_type(self) -> None: + """Test invalid CRS type raises ValidationError""" + invalid_data: dict[str, Any] = { + "type": "InvalidCRS", # Invalid type + "name": "Invalid CRS", + } + with pytest.raises(ValidationError): + GeodeticCRS(**invalid_data) + + def test_missing_required_fields(self) -> None: + """Test missing required fields raise ValidationError""" + # Missing name for CRS + with pytest.raises(ValidationError): + GeodeticCRS(type="GeographicCRS") + + # Missing ellipsoid for geodetic reference frame + with pytest.raises(ValidationError): + GeodeticReferenceFrame(type="GeodeticReferenceFrame", name="Test Datum") + + def test_mutually_exclusive_fields(self) -> None: + """Test that mutually exclusive fields are properly validated""" + # Cannot have both id and ids + invalid_data: dict[str, Any] = { + "type": "Unit", + "name": "metre", + "conversion_factor": 1.0, + "id": {"authority": "EPSG", "code": 9001}, + "ids": [{"authority": "EPSG", "code": 9001}], + } + # Note: This specific validation would need to be implemented in the model + # For now, we'll just ensure the model can be created with either field + + with pytest.raises(ValidationError): + Unit(**invalid_data) + + # Valid with id only + valid_with_id: dict[str, Any] = { + "type": "Unit", + "name": "metre", + "conversion_factor": 1.0, + "id": {"authority": "EPSG", "code": 9001}, + } + unit: Unit = Unit(**valid_with_id) + assert unit.id is not None + assert unit.ids is None + + # Valid with ids only + valid_with_ids: dict[str, Any] = { + "type": "Unit", + "name": "metre", + "conversion_factor": 1.0, + "ids": [{"authority": "EPSG", "code": 9001}], + } + unit = Unit(**valid_with_ids) + assert unit.ids is not None + assert unit.id is None + + +class TestSerializationDeserialization: + """Test JSON serialization and deserialization""" + + def test_round_trip_serialization(self) -> None: + """Test that models can be serialized to JSON and back""" + # Create a simple CRS + crs_data: dict[str, Any] = { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + }, + }, + } + + # Create model instance + original_crs: GeodeticCRS = GeodeticCRS(**crs_data) + + # Deserialize back to model + json_data: dict[str, Any] = original_crs.model_dump() + reconstructed_crs: GeodeticCRS = GeodeticCRS(**json_data) + + # Verify they're equivalent + assert reconstructed_crs.name == original_crs.name + assert reconstructed_crs.datum.name == original_crs.datum.name + assert ( + reconstructed_crs.datum.ellipsoid.name == original_crs.datum.ellipsoid.name + ) + + def test_projjson_union_type(self) -> None: + """Test that ProjJSON union type works correctly""" + # Test with different types that should all be valid ProjJSON + + # Ellipsoid + ellipsoid_data: dict[str, Any] = { + "type": "Ellipsoid", + "name": "WGS 84", + "semi_major_axis": 6378137.0, + "inverse_flattening": 298.257223563, + } + ellipsoid: Ellipsoid = Ellipsoid(**ellipsoid_data) + assert ellipsoid.name == "WGS 84" + + # CRS + crs_data: dict[str, Any] = { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": ellipsoid_data, + }, + } + crs: GeodeticCRS = GeodeticCRS(**crs_data) + assert crs.name == "WGS 84" + + +class TestRoundTripSerialization: + """Test round-trip serialization with real PROJ JSON examples.""" + + def test_projected_crs_round_trip(self, projected_crs_json: dict[str, Any]) -> None: + """Test round-trip serialization of projected CRS example.""" + # Parse JSON to Pydantic model + from eopf_geozarr.data_api.geozarr.projjson import ProjectedCRS + + # Create model from JSON + original_crs: ProjectedCRS = ProjectedCRS(**projected_crs_json) + + # Serialize back to dict + serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True) + + # Create model from serialized data + round_trip_crs: ProjectedCRS = ProjectedCRS(**serialized) + + # Verify key properties are preserved + assert round_trip_crs.name == original_crs.name + assert round_trip_crs.type == original_crs.type + assert round_trip_crs.base_crs.name == original_crs.base_crs.name + assert round_trip_crs.conversion.name == original_crs.conversion.name + if original_crs.id: + assert round_trip_crs.id.authority == original_crs.id.authority + assert round_trip_crs.id.code == original_crs.id.code + + def test_bound_crs_round_trip(self, bound_crs_json: dict[str, Any]) -> None: + """Test round-trip serialization of bound CRS example.""" + from eopf_geozarr.data_api.geozarr.projjson import BoundCRS + + # Create model from JSON + original_crs: BoundCRS = BoundCRS(**bound_crs_json) + + # Serialize back to dict + serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True) + + # Create model from serialized data + round_trip_crs: BoundCRS = BoundCRS(**serialized) + + # Verify key properties are preserved + assert round_trip_crs.type == original_crs.type + assert round_trip_crs.source_crs.name == original_crs.source_crs.name + assert round_trip_crs.target_crs.name == original_crs.target_crs.name + assert round_trip_crs.transformation.name == original_crs.transformation.name + + def test_compound_crs_round_trip(self, compound_crs_json: dict[str, Any]) -> None: + """Test round-trip serialization of compound CRS example.""" + from eopf_geozarr.data_api.geozarr.projjson import CompoundCRS + + # Create model from JSON + original_crs: CompoundCRS = CompoundCRS(**compound_crs_json) + + # Serialize back to dict + serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True) + + # Create model from serialized data + round_trip_crs: CompoundCRS = CompoundCRS(**serialized) + + # Verify key properties are preserved + assert round_trip_crs.name == original_crs.name + assert round_trip_crs.type == original_crs.type + assert len(round_trip_crs.components) == len(original_crs.components) + for i, component in enumerate(round_trip_crs.components): + assert component.name == original_crs.components[i].name + + def test_datum_ensemble_round_trip( + self, datum_ensemble_json: dict[str, Any] + ) -> None: + """Test round-trip serialization of datum ensemble example.""" + from eopf_geozarr.data_api.geozarr.projjson import GeodeticCRS + + # Create model from JSON + original_crs: GeodeticCRS = GeodeticCRS(**datum_ensemble_json) + + # Serialize back to dict + serialized: dict[str, Any] = original_crs.model_dump(exclude_none=True) + + # Create model from serialized data + round_trip_crs: GeodeticCRS = GeodeticCRS(**serialized) + + # Verify key properties are preserved + assert round_trip_crs.name == original_crs.name + assert round_trip_crs.type == original_crs.type + if original_crs.datum_ensemble: + assert ( + round_trip_crs.datum_ensemble.name == original_crs.datum_ensemble.name + ) + assert len(round_trip_crs.datum_ensemble.members) == len( + original_crs.datum_ensemble.members + ) + + def test_transformation_round_trip( + self, transformation_json: dict[str, Any] + ) -> None: + """Test round-trip serialization of transformation example.""" + from eopf_geozarr.data_api.geozarr.projjson import SingleOperation + + # Create model from JSON + original_op: SingleOperation = SingleOperation(**transformation_json) + + # Serialize back to dict + serialized: dict[str, Any] = original_op.model_dump(exclude_none=True) + + # Create model from serialized data + round_trip_op: SingleOperation = SingleOperation(**serialized) + + # Verify key properties are preserved + assert round_trip_op.name == original_op.name + assert round_trip_op.type == original_op.type + assert round_trip_op.method.name == original_op.method.name + if original_op.parameters: + assert len(round_trip_op.parameters) == len(original_op.parameters) + + def test_all_examples_round_trip( + self, all_projjson_examples: list[dict[str, Any]] + ) -> None: + """Test that all PROJ JSON examples can be round-tripped without error.""" + from eopf_geozarr.data_api.geozarr.projjson import ( + BoundCRS, + CompoundCRS, + CoordinateMetadata, + DatumEnsemble, + Ellipsoid, + GeodeticCRS, + PrimeMeridian, + ProjectedCRS, + SingleOperation, + TemporalCRS, + VerticalCRS, + ) + + # Map types to model classes + type_mapping = { + "GeographicCRS": GeodeticCRS, + "GeodeticCRS": GeodeticCRS, + "ProjectedCRS": ProjectedCRS, + "BoundCRS": BoundCRS, + "CompoundCRS": CompoundCRS, + "VerticalCRS": VerticalCRS, + "TemporalCRS": TemporalCRS, + "Transformation": SingleOperation, + "Conversion": SingleOperation, + "DatumEnsemble": DatumEnsemble, + "Ellipsoid": Ellipsoid, + "PrimeMeridian": PrimeMeridian, + "CoordinateMetadata": CoordinateMetadata, + } + + successful_round_trips = 0 + total_examples = len(all_projjson_examples) + + for example in all_projjson_examples: + try: + # Get the model class based on type + obj_type = example.get("type") + if obj_type not in type_mapping: + continue # Skip unknown types + + model_class = type_mapping[obj_type] + + # Create model from JSON + original_model = model_class(**example) + + # Serialize back to dict + serialized = original_model.model_dump(exclude_none=True) + + # Create model from serialized data + round_trip_model = model_class(**serialized) + + # Basic verification that the round-trip worked + assert round_trip_model.type == original_model.type + if hasattr(original_model, "name") and original_model.name: + assert round_trip_model.name == original_model.name + + successful_round_trips += 1 + + except Exception as e: + # For debugging, print which example failed + print( + f"Failed to round-trip example with type {example.get('type', 'unknown')}: {e}" + ) + raise # Re-raise to fail the test + + # Ensure we successfully round-tripped at least some examples + assert successful_round_trips > 0, "No examples were successfully round-tripped" + print( + f"Successfully round-tripped {successful_round_trips}/{total_examples} examples" + ) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_data_api/test_v3.py b/tests/test_data_api/test_v3.py index 6ca049f..ac556a0 100644 --- a/tests/test_data_api/test_v3.py +++ b/tests/test_data_api/test_v3.py @@ -12,8 +12,6 @@ check_valid_coordinates, ) -from .conftest import example_group - class TestCheckValidCoordinates: @staticmethod @@ -63,7 +61,7 @@ def test_invalid_coordinates( check_valid_coordinates(group) -def test_dataarray_round_trip() -> None: +def test_dataarray_round_trip(example_group) -> None: """ Ensure that we can round-trip dataarray attributes through the `Multiscales` model. """ @@ -75,7 +73,7 @@ def test_dataarray_round_trip() -> None: assert DataArray(**model_json).model_dump() == model_json -def test_multiscale_attrs_round_trip() -> None: +def test_multiscale_attrs_round_trip(example_group) -> None: """ Test that multiscale datasets round-trip through the `Multiscales` model """ diff --git a/tests/test_projjson.py b/tests/test_projjson.py new file mode 100644 index 0000000..e69de29 From 0fc3d09ecfb11db153ea9f6d411e76697f8c65fe Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 1 Oct 2025 10:03:27 +0200 Subject: [PATCH 2/3] update geo proj attrs --- src/eopf_geozarr/data_api/geozarr/common.py | 29 +++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/eopf_geozarr/data_api/geozarr/common.py b/src/eopf_geozarr/data_api/geozarr/common.py index 29a4998..76bc4b4 100644 --- a/src/eopf_geozarr/data_api/geozarr/common.py +++ b/src/eopf_geozarr/data_api/geozarr/common.py @@ -3,16 +3,32 @@ import io import urllib import urllib.request +from dataclasses import dataclass from typing import Annotated, Any, Mapping, TypeVar from cf_xarray.utils import parse_cf_standard_name_table -from pydantic import AfterValidator, BaseModel +from pydantic import AfterValidator, BaseModel, Field from pydantic.experimental.missing_sentinel import MISSING -from typing_extensions import Protocol, runtime_checkable +from typing_extensions import Final, Literal, Protocol, runtime_checkable +from eopf_geozarr.data_api.geozarr.projjson import ProjJSON from eopf_geozarr.data_api.geozarr.types import ResamplingMethod +@dataclass(frozen=True) +class UNSET_TYPE: + """ + Sentinel value to indicate that a value is not set. + """ + + ... + + +UNSET = UNSET_TYPE() + +GEO_PROJ_VERSION: Final = "0.1" + + class ProjAttrs(BaseModel, extra="allow"): """ Zarr attributes for coordinate reference system (CRS) encoding. @@ -29,6 +45,15 @@ class ProjAttrs(BaseModel, extra="allow"): bbox: """ + version: Literal["0.1"] = "0.1" + code: str | None = Field(pattern="^[A-Z]+:[0-9]+$") + wkt2: str | None = None + projjson: ProjJSON | None + bbox: tuple[float, float, float, float] | UNSET_TYPE = UNSET + transform: tuple[float, float, float, float, float, float] | UNSET_TYPE = UNSET + # TODO: enclosing object must validate these properties against the arrays + spatial_dimensions: tuple[str, str] | UNSET_TYPE = UNSET + class BaseDataArrayAttrs(BaseModel, extra="allow"): """ From 5d1be4df07bd76419fa3f0f5b92e9a745bb0134d Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 2 Oct 2025 15:54:04 +0200 Subject: [PATCH 3/3] add requirement that one of the 3 CRS options be specified --- src/eopf_geozarr/data_api/geozarr/common.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/eopf_geozarr/data_api/geozarr/common.py b/src/eopf_geozarr/data_api/geozarr/common.py index 76bc4b4..dfd9f25 100644 --- a/src/eopf_geozarr/data_api/geozarr/common.py +++ b/src/eopf_geozarr/data_api/geozarr/common.py @@ -4,10 +4,10 @@ import urllib import urllib.request from dataclasses import dataclass -from typing import Annotated, Any, Mapping, TypeVar +from typing import Annotated, Any, Mapping, Self, TypeVar from cf_xarray.utils import parse_cf_standard_name_table -from pydantic import AfterValidator, BaseModel, Field +from pydantic import AfterValidator, BaseModel, Field, model_validator from pydantic.experimental.missing_sentinel import MISSING from typing_extensions import Final, Literal, Protocol, runtime_checkable @@ -54,6 +54,12 @@ class ProjAttrs(BaseModel, extra="allow"): # TODO: enclosing object must validate these properties against the arrays spatial_dimensions: tuple[str, str] | UNSET_TYPE = UNSET + @model_validator(mode="after") + def check_one_of(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.") + return self + class BaseDataArrayAttrs(BaseModel, extra="allow"): """