From 489708de6410ffc2a8d876eb3b59c26f09181174 Mon Sep 17 00:00:00 2001 From: Tik Date: Sat, 13 Jun 2026 14:18:39 +0300 Subject: [PATCH 01/10] implementing HEALPIXGeoProvider using healpix-geo to support nested, ring and zuniq indexing schema. --- .../healpixgeo_dggrs_provider.py | 190 ++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 191 insertions(+) create mode 100644 pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py diff --git a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py new file mode 100644 index 0000000..f71cb6f --- /dev/null +++ b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py @@ -0,0 +1,190 @@ +# here should be DGGRID related functions and methods +# DGGRID ISEA7H resolutions + +from pydggsapi.dependencies.dggrs_providers.abstract_dggrs_provider import AbstractDGGRSProvider, ZoneIdRepresentationType +from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint +from pydggsapi.schemas.api.dggrs_providers import ( + DGGRSProviderZoneInfoReturn, + DGGRSProviderZonesListReturn, + DGGRSProviderGetRelativeZoneLevelsReturn, + DGGRSProviderConversionReturn, + DGGRSProviderZonesElement, +) +from pydggsapi.schemas.ogc_dggs.common_ogc_dggs_api import ReturnGeometryTypes +from healpix_geo.auto import Grid as healpixgrid +import shapely +import logging +from typing import Any, List, Union, Optional, get_args + +logger = logging.getLogger() + +# Implementation assumption: +# - for nested index, there are no functions to return the refinement level of a zone id, +# so the only way to do that is using zuniq index. +# + + +supported_indexing_schema = ["nested", "ring", "zuniq"] +supported_ellipsoid = ["sphere", "wgs84"] + +# healpix_geo doesn't support zone statistics function yet +# unit : square meter and meter +healpix_zone_statistics = { + 0 : {"area" : 42506000000000.0000 , "cls": 6519662.5680 }, + 1 : { "area" : 10626500000000.0000 , "cls": 3259831.2840 }, + 2 : { "area" : 2656625000000.0000 , "cls": 1629915.6420 }, + 3 : { "area" : 664156250000.0000 , "cls": 814957.8210 }, + 4 : { "area" : 166039062500.0000 , "cls": 407478.9105 }, + 5 : { "area" : 41509765625.0000 , "cls": 203739.4552 }, + 6 : { "area" : 10377441406.2500 , "cls": 101869.7276 }, + 7 : { "area" : 2594360351.5625 , "cls": 50934.8638 }, + 8 : { "area" : 648590087.8906 , "cls": 25467.4319 }, + 9 : { "area" : 162147521.9727 , "cls": 12733.7160 }, + 10 : { "area" : 40536880.4932 , "cls": 6366.8580 }, + 11 : { "area" : 10134220.1233 , "cls": 3183.4290 }, + 12 : { "area" : 2533555.0308 , "cls": 1591.7145 }, + 13 : { "area" : 633388.7577 , "cls": 795.8572 }, + 14 : { "area" : 158347.1894 , "cls": 397.9286 }, + 15 : { "area" : 39586.7974 , "cls": 198.9643 }, + 16 : { "area" : 9896.6993 , "cls": 99.4822 }, + 17 : { "area" : 2474.1748 , "cls": 49.7411 }, + 18 : { "area" : 618.5437 , "cls": 24.8705 }, + 19 : { "area" : 154.6359 , "cls": 12.4353 }, + 20 : { "area" : 38.6590 , "cls": 6.2176 }, + 21 : { "area" : 9.6647 , "cls": 3.1088 }, + 22 : { "area" : 2.4162 , "cls": 1.5544 }, + 23 : { "area" : 0.6040 , "cls": 0.7772 }, + 24 : { "area" : 0.1510 , "cls": 0.3886 }, + 25 : { "area" : 0.0377 , "cls": 0.1943 }, + 26 : { "area" : 0.0094 , "cls": 0.0972 }, + 27 : { "area" : 0.0024, "cls": 0.0486 }, + 28 : { "area" : 0.0006 , "cls": 0.0243 }, + 29 : { "area" : 0.0001 , "cls": 0.0121 }} + + +class HEALPIXGeoProvider(AbstractDGGRSProvider): + def __init__(self, **params): + self.indexing_schema = params.get("indexing_schema", "nested").lower() + self.ellipsoid = params.get("ellipsoid", "wgs84").lower() + if (self.indexing_schema not in supported_indexing_schema): + raise ValueError(f"{__name__} {self.indexing_schema} not supported") + if (self.ellipsoid not in supported_ellipsoid): + raise ValueError(f"{__name__} {self.indexing_schema} not supported") + + def convert(self, zoneIds: List[str], targedggrs: str, + zone_id_repr: ZoneIdRepresentationType = 'textual') -> DGGRSProviderConversionReturn: + raise NotImplementedError + + # at the time of implementation, healpix_geo doesn't support textural repr of zone id + # so assume the input is just uint64 in str + def zone_id_from_textual(self, cellIds: List[str], zone_id_repr: str) -> List[Any]: + if (zone_id_repr not in get_args(ZoneIdRepresentationType)): + raise ValueError("{__name__} {zone_id_repr} representation is not supported.") + if (len(cellIds) == 0): + return [] + if (zone_id_repr == "textual"): + return cellIds + if (zone_id_repr == "int"): + return [int(z) for z in cellIds] + if (zone_id_repr == "hexstring"): + raise ValueError("{__name__} doesn't support hexstring zone id representation") + + # at the time of implementation, healpix_geo doesn't support textural repr of zone id + # so assume the output is just uint64 in str + def zone_id_to_textual(self, cellIds: List[Any], zone_id_repr: str, refinement_level=None) -> List[str]: + if (zone_id_repr not in get_args(ZoneIdRepresentationType)): + raise ValueError("{__name__} {zone_id_repr} representation is not supported.") + if (len(cellIds) == 0): + return [] + if (zone_id_repr == "textual"): + return cellIds + if (zone_id_repr == "int"): + # get_data return zone id in string format + return [str(z) for z in cellIds] + if (zone_id_repr == "hexstring"): + raise ValueError("{__name__} doesn't support hexstring zone id representation") + + def get_cls_by_zone_level(self, zone_level: int) -> float: + return healpix_zone_statistics[zone_level]["cls"] + + def get_zone_level_by_cls(self, cls_km: float): + for k, v in healpix_zone_statistics.items(): + if (v["cls"] < cls_km * 1000): + return k + + def get_cells_zone_level(self, cellIds: List[str]) -> List[int]: + cellId = self.mygrid.getZoneFromTextID(cellIds[0]) + return [self.mygrid.getZoneLevel(cellId)] + + def get_relative_zonelevels(self, cellId: str, base_level: int, zone_levels: List[int], + geometry: Optional[ReturnGeometryTypes] = 'zone-region') -> DGGRSProviderGetRelativeZoneLevelsReturn: + children = {} + geometry = geometry.lower() if (geometry is not None) else geometry + cellId = self.zone_id_from_textual(cellId) + if (self.) + for z in zone_levels: + subzoneIds = self.mygrid.getSubZones(cellId, (z - base_level)) + subzones_geometry = None + if (geometry is not None): + subzones_geometry = [generateZoneGeometry(self.mygrid, cellId, None, False if (geometry == 'zone-region') else True) + for cellId in subzoneIds] + subzoneIds = [self.mygrid.getZoneTextID(id_) for id_ in subzoneIds] + children[z] = DGGRSProviderZonesElement(**{'zoneIds': subzoneIds, + 'geometry': subzones_geometry}) + return DGGRSProviderGetRelativeZoneLevelsReturn(relative_zonelevels=children) + + def zonesinfo(self, cellIds: List[str]) -> DGGRSProviderZoneInfoReturn: + zone_level = self.get_cells_zone_level(cellIds)[0] + cellIds = [self.mygrid.getZoneFromTextID(cellId) for cellId in cellIds] + try: + centroids = [generateZoneGeometry(self.mygrid, cellId, None, True) + for cellId in cellIds] + #centroids = [GeoJSONPoint(**eval(shapely.to_geojson(c))) for c in centroids] + hex_vertices = [generateZoneGeometry(self.mygrid, cellId, None, False) + for cellId in cellIds] + #hex_vertices = [GeoJSONPolygon(**eval(shapely.to_geojson(g))) for g in hex_vertices] + extents = [generateZoneExtent(self.mygrid, cellId) for cellId in cellIds] + extents = [b.bounds for b in extents] + except Exception as e: + logger.error(f'{__name__} zone id {cellIds} convert failed, {e}') + raise Exception(f'{__name__} zone id {cellIds} convert failed, {e}') + return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'hexagon', + 'centroids': centroids, 'geometry': hex_vertices, 'bbox': extents, + 'areaMetersSquare': self.mygrid.getRefZoneArea(zone_level)}) + + def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone: Union[str, int, None], + returngeometry: ReturnGeometryTypes, compact: bool = True) -> List[str]: + if (bbox is not None): + try: + bbox = shapely.bounds(bbox) + geoextent = GeoExtent(GeoPoint(bbox[1], bbox[0]), GeoPoint(bbox[3], bbox[2])) + zones_list = self.mygrid.listZones(zone_level, geoextent) + zones_list = set(int(z) for z in zones_list) + except Exception as e: + logger.error(f'{__name__} query zones list, bbox: {bbox} dggrid convert failed :{e}') + raise Exception(f"{__name__} query zones list, bbox: {bbox} dggrid convert failed {e}") + logger.info(f'{__name__} query zones list, number of hexagons: {len(zones_list)}') + if (parent_zone is not None): + try: + parent_zone_level = self.get_cells_zone_level([parent_zone])[0] + parent_zone = self.mygrid.getZoneFromTextID(parent_zone) + subzones_list = self.mygrid.getSubZones(parent_zone, (zone_level - parent_zone_level)) + subzones_list = set(int(z) for z in subzones_list) + zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list + except Exception as e: + logger.error(f'{__name__} query zones list, parent_zone: {parent_zone} get children failed {e}') + raise Exception(f'parent_zone: {parent_zone} get children failed {e}') + if (len(zones_list) == 0): + raise Exception(f"{__name__} Parent zone {parent_zone} is not with in bbox: {bbox} at zone level {zone_level}") + if (compact): + compact_list = Array("") + [compact_list.add(int(z)) for z in zones_list] + self.mygrid.compactZones(compact_list) + zones_list = [int(z) for z in compact_list] + logger.info(f'{__name__} query zones list, compact : {len(zones_list)}') + zones_geometry = [generateZoneGeometry(self.mygrid, z, None, False if (returngeometry == 'zone-region') else True) for z in zones_list] + returnedAreaMetersSquare = [self.mygrid.getZoneArea(z) for z in zones_list] + zones_list = [self.mygrid.getZoneTextID(z) for z in zones_list] + return DGGRSProviderZonesListReturn(**{'zones': zones_list, + 'geometry': zones_geometry, + 'returnedAreaMetersSquare': returnedAreaMetersSquare}) diff --git a/pyproject.toml b/pyproject.toml index 1ffa55b..ab9dfff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ pytest-env = "^1.6.0" zarr = "^3.2.1" s3fs = ">=2026.2.0,<2027.0.0" gcsfs = ">=2026.2.0,<2027.0.0" +healpix-geo = "^0.1.2" [tool.poetry.group.dev.dependencies] From ce7e03df3479246522cf22b3e2ff91a32d3f2153 Mon Sep 17 00:00:00 2001 From: Tik Date: Sat, 13 Jun 2026 22:55:04 +0300 Subject: [PATCH 02/10] HEALPixZuniqProvider implemenetation, only tested with mvt tiles. --- .../healpixgeo_dggrs_provider.py | 108 ++++++++++++------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py index f71cb6f..1096546 100644 --- a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py +++ b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py @@ -11,20 +11,23 @@ DGGRSProviderZonesElement, ) from pydggsapi.schemas.ogc_dggs.common_ogc_dggs_api import ReturnGeometryTypes -from healpix_geo.auto import Grid as healpixgrid +import healpix_geo +import numpy as np import shapely +import json import logging from typing import Any, List, Union, Optional, get_args logger = logging.getLogger() # Implementation assumption: -# - for nested index, there are no functions to return the refinement level of a zone id, -# so the only way to do that is using zuniq index. -# +# - For nested index, there are no functions to return the refinement level of a zone id, +# so the only way to do that is using zuniq index. In resultthe implementation only support zuniq index. +# - healpix-geo doesn't support textural zone id representation, +# so I assume the input zone id is just the uint64 in str format -supported_indexing_schema = ["nested", "ring", "zuniq"] +supported_indexing_schema = ["zuniq"] supported_ellipsoid = ["sphere", "wgs84"] # healpix_geo doesn't support zone statistics function yet @@ -62,14 +65,15 @@ 29 : { "area" : 0.0001 , "cls": 0.0121 }} -class HEALPIXGeoProvider(AbstractDGGRSProvider): +class HEALPixZuniqProvider(AbstractDGGRSProvider): def __init__(self, **params): - self.indexing_schema = params.get("indexing_schema", "nested").lower() + self.indexing_schema = params.get("indexing_schema", "zuniq").lower() self.ellipsoid = params.get("ellipsoid", "wgs84").lower() if (self.indexing_schema not in supported_indexing_schema): raise ValueError(f"{__name__} {self.indexing_schema} not supported") if (self.ellipsoid not in supported_ellipsoid): raise ValueError(f"{__name__} {self.indexing_schema} not supported") + self.ellipsoid = self.ellipsoid.upper() def convert(self, zoneIds: List[str], targedggrs: str, zone_id_repr: ZoneIdRepresentationType = 'textual') -> DGGRSProviderConversionReturn: @@ -113,43 +117,46 @@ def get_zone_level_by_cls(self, cls_km: float): return k def get_cells_zone_level(self, cellIds: List[str]) -> List[int]: - cellId = self.mygrid.getZoneFromTextID(cellIds[0]) - return [self.mygrid.getZoneLevel(cellId)] + _, depth = healpix_geo.zuniq.to_nested(cellIds) + if (isinstance(depth, int)): + depth = [depth] + else: + depth = depth.tolist() + return depth def get_relative_zonelevels(self, cellId: str, base_level: int, zone_levels: List[int], geometry: Optional[ReturnGeometryTypes] = 'zone-region') -> DGGRSProviderGetRelativeZoneLevelsReturn: children = {} geometry = geometry.lower() if (geometry is not None) else geometry - cellId = self.zone_id_from_textual(cellId) - if (self.) + cellId = self.zone_id_from_textual([cellId], "int") + # utilising the zoom_to function of nested index + nested_cellId, nested_rf = healpix_geo.zuniq.to_nested(np.array(cellId)) for z in zone_levels: - subzoneIds = self.mygrid.getSubZones(cellId, (z - base_level)) + subzone_nestedIds = healpix_geo.nested.zoom_to(nested_cellId, nested_rf, z) subzones_geometry = None if (geometry is not None): - subzones_geometry = [generateZoneGeometry(self.mygrid, cellId, None, False if (geometry == 'zone-region') else True) - for cellId in subzoneIds] - subzoneIds = [self.mygrid.getZoneTextID(id_) for id_ in subzoneIds] + subzones_geometry = [self._nestedindex_generateZoneGeometry(cellId, z, False if (geometry == 'zone-region') else True) + for cellId in subzone_nestedIds] + subzoneIds = self.zone_id_to_textual(subzone_nestedIds.tolist(), "int") children[z] = DGGRSProviderZonesElement(**{'zoneIds': subzoneIds, 'geometry': subzones_geometry}) return DGGRSProviderGetRelativeZoneLevelsReturn(relative_zonelevels=children) def zonesinfo(self, cellIds: List[str]) -> DGGRSProviderZoneInfoReturn: zone_level = self.get_cells_zone_level(cellIds)[0] - cellIds = [self.mygrid.getZoneFromTextID(cellId) for cellId in cellIds] + cellIds = self.zone_id_from_textual(cellIds, "int") try: - centroids = [generateZoneGeometry(self.mygrid, cellId, None, True) + centroids = [self._generateZoneGeometry(cellId, True) for cellId in cellIds] - #centroids = [GeoJSONPoint(**eval(shapely.to_geojson(c))) for c in centroids] - hex_vertices = [generateZoneGeometry(self.mygrid, cellId, None, False) - for cellId in cellIds] - #hex_vertices = [GeoJSONPolygon(**eval(shapely.to_geojson(g))) for g in hex_vertices] - extents = [generateZoneExtent(self.mygrid, cellId) for cellId in cellIds] + square_vertices = [self._generateZoneGeometry(cellId, False) + for cellId in cellIds] + extents = [shapely.from_geojson(json.dumps(geojson)) for geojson in square_vertices] extents = [b.bounds for b in extents] except Exception as e: logger.error(f'{__name__} zone id {cellIds} convert failed, {e}') raise Exception(f'{__name__} zone id {cellIds} convert failed, {e}') return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'hexagon', - 'centroids': centroids, 'geometry': hex_vertices, 'bbox': extents, + 'centroids': centroids, 'geometry': square_vertices, 'bbox': extents, 'areaMetersSquare': self.mygrid.getRefZoneArea(zone_level)}) def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone: Union[str, int, None], @@ -157,9 +164,8 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone if (bbox is not None): try: bbox = shapely.bounds(bbox) - geoextent = GeoExtent(GeoPoint(bbox[1], bbox[0]), GeoPoint(bbox[3], bbox[2])) - zones_list = self.mygrid.listZones(zone_level, geoextent) - zones_list = set(int(z) for z in zones_list) + zones_list, _ = healpix_geo.zuniq.zone_coverage(tuple(bbox.tolist()), zone_level, ellipsoid=self.ellipsoid, flat=False) + zones_list = set(zones_list) except Exception as e: logger.error(f'{__name__} query zones list, bbox: {bbox} dggrid convert failed :{e}') raise Exception(f"{__name__} query zones list, bbox: {bbox} dggrid convert failed {e}") @@ -167,24 +173,50 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone if (parent_zone is not None): try: parent_zone_level = self.get_cells_zone_level([parent_zone])[0] - parent_zone = self.mygrid.getZoneFromTextID(parent_zone) - subzones_list = self.mygrid.getSubZones(parent_zone, (zone_level - parent_zone_level)) - subzones_list = set(int(z) for z in subzones_list) + parent_zone = self.zone_id_from_textual([parent_zone], "int") + parent_zone_nestedId, _ = healpix_geo.zuniq(parent_zone) + subzones_list = healpix_geo.nested.zoome_to(parent_zone_nestedId, parent_zone_level, zone_level) + subzones_list = healpix_geo.zuniq.from_nested(subzones_list, zone_level) zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list except Exception as e: logger.error(f'{__name__} query zones list, parent_zone: {parent_zone} get children failed {e}') raise Exception(f'parent_zone: {parent_zone} get children failed {e}') if (len(zones_list) == 0): raise Exception(f"{__name__} Parent zone {parent_zone} is not with in bbox: {bbox} at zone level {zone_level}") - if (compact): - compact_list = Array("") - [compact_list.add(int(z)) for z in zones_list] - self.mygrid.compactZones(compact_list) - zones_list = [int(z) for z in compact_list] - logger.info(f'{__name__} query zones list, compact : {len(zones_list)}') - zones_geometry = [generateZoneGeometry(self.mygrid, z, None, False if (returngeometry == 'zone-region') else True) for z in zones_list] - returnedAreaMetersSquare = [self.mygrid.getZoneArea(z) for z in zones_list] - zones_list = [self.mygrid.getZoneTextID(z) for z in zones_list] + # TODO: compact zones + zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] + returnedAreaMetersSquare = [healpix_zone_statistics[zone_level]['area']] * len(zones_list) + zones_list = self.zone_id_to_textual(zones_list, "int") return DGGRSProviderZonesListReturn(**{'zones': zones_list, 'geometry': zones_geometry, 'returnedAreaMetersSquare': returnedAreaMetersSquare}) + + def _nestedindex_generateZoneGeometry(self, zoneId: int, refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.nested.healpix_to_lonlat(np.array([zoneId]), refinement_level, self.ellipsoid) + + return GeoJSONPoint(type="Point", coordinates=(lon[0][0], lat[0][1])) + else: + lon, lat = healpix_geo.nested.vertices([zoneId], refinement_level, self.ellipsoid) + vertices = np.squeeze(np.stack([lon, lat], axis=-1)) + coordinates = [] + for i in range(vertices.shape[0]): + coordinates.append((vertices[i][0], vertices[i][1])) + # to make the polygon a closed linestring + coordinates.append((vertices[0][0], vertices[0][1])) + return GeoJSONPolygon(type="Polygon", coordinates=[coordinates]) + + def _generateZoneGeometry(self, zoneId: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.zuniq.healpix_to_lonlat(np.array([zoneId]), self.ellipsoid) + return GeoJSONPoint(type="Point", coordinates=(lon[0][0], lat[0][1])) + else: + lon, lat = healpix_geo.zuniq.vertices([zoneId], self.ellipsoid) + vertices = np.squeeze(np.stack([lon, lat], axis=-1)) + coordinates = [] + for i in range(vertices.shape[0]): + coordinates.append((vertices[i][0], vertices[i][1])) + # to make the polygon a closed linestring + coordinates.append((vertices[0][0], vertices[0][1])) + return GeoJSONPolygon(type="Polygon", coordinates=[coordinates]) + From 3fe270b2c34ceb8b0d4e1ac4b87e269f4f8cda69 Mon Sep 17 00:00:00 2001 From: Tik Date: Sun, 14 Jun 2026 15:54:57 +0300 Subject: [PATCH 03/10] splits the selection into two parts for a slightly better performance. - select data using `.sel` with method `pad`, to reduce the dataset first.(Fuzzy match) - Then use query to perform actual selection. --- .../collections_providers/zarr_collection_provider.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py b/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py index 9c7af4d..c30b6c7 100644 --- a/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py +++ b/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py @@ -107,7 +107,10 @@ def get_data(self, zoneIds: List[Any], res: int, datasource_id: str, else: cols = OrderedSet(ds.data_vars) if ("*" in datasource.data_cols) else OrderedSet(datasource.data_cols) cols = list(cols - OrderedSet(datasource.exclude_data_cols)) - zarr_result = ds.query({id_col: f'{id_col} in {zoneIds}'}) + # fuzzy selection to reduce the dataset size + zarr_result = ds.sel({id_col: zoneIds}, method='pad').drop_duplicates(id_col) + # second selection to make it exact + zarr_result = zarr_result.query({id_col: f'{id_col} in {zoneIds}'}) zarr_result = zarr_result[cols] except Exception as e: # Zarr will raise exception if nothing matched From 8207b1511c2a1d3a4151d78afbdd70b88fe2bc7d Mon Sep 17 00:00:00 2001 From: Tik Date: Mon, 21 Sep 2026 13:41:20 +0300 Subject: [PATCH 04/10] introduced a new "HealpixGeoNestedProvider" for nested index. --- .../healpixgeo_dggrs_provider.py | 161 ++++++++++++++++-- 1 file changed, 149 insertions(+), 12 deletions(-) diff --git a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py index 1096546..4792530 100644 --- a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py +++ b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py @@ -1,6 +1,3 @@ -# here should be DGGRID related functions and methods -# DGGRID ISEA7H resolutions - from pydggsapi.dependencies.dggrs_providers.abstract_dggrs_provider import AbstractDGGRSProvider, ZoneIdRepresentationType from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint from pydggsapi.schemas.api.dggrs_providers import ( @@ -21,13 +18,13 @@ logger = logging.getLogger() # Implementation assumption: -# - For nested index, there are no functions to return the refinement level of a zone id, -# so the only way to do that is using zuniq index. In resultthe implementation only support zuniq index. # - healpix-geo doesn't support textural zone id representation, # so I assume the input zone id is just the uint64 in str format +# - from the discussion in https://github.com/opengeospatial/ogcapi-discrete-global-grid-systems/issues/110 +# the textual ZIRS of the nested index should be in the form of "{refinement level}_{nested index}". -supported_indexing_schema = ["zuniq"] +supported_indexing_schema = ["zuniq", "nested"] supported_ellipsoid = ["sphere", "wgs84"] # healpix_geo doesn't support zone statistics function yet @@ -65,14 +62,152 @@ 29 : { "area" : 0.0001 , "cls": 0.0121 }} -class HEALPixZuniqProvider(AbstractDGGRSProvider): +class HealpixGeoNestedProvider(AbstractDGGRSProvider): def __init__(self, **params): - self.indexing_schema = params.get("indexing_schema", "zuniq").lower() + self.indexing_schema = params.get("indexing_schema", "nested").lower() self.ellipsoid = params.get("ellipsoid", "wgs84").lower() if (self.indexing_schema not in supported_indexing_schema): raise ValueError(f"{__name__} {self.indexing_schema} not supported") if (self.ellipsoid not in supported_ellipsoid): + raise ValueError(f"{__name__} {self.ellipsoid} not supported") + self.ellipsoid = self.ellipsoid.upper() + + def convert(self, zoneIds: List[str], targedggrs: str, + zone_id_repr: ZoneIdRepresentationType = 'textual') -> DGGRSProviderConversionReturn: + raise NotImplementedError + + # the textual ZIRS of the nested index should be in the form of "{refinement level}_{nested index}". + def zone_id_from_textual(self, cellIds: List[str], zone_id_repr: str) -> List[Any]: + if (zone_id_repr not in get_args(ZoneIdRepresentationType)): + raise ValueError("{__name__} {zone_id_repr} representation is not supported.") + if (len(cellIds) == 0): + return [] + if (zone_id_repr == "textual"): + return cellIds + if (zone_id_repr == "int"): + cellIds = list(map(lambda x: x.split("_")[1], cellIds)) + cellIds = [int(cell_id) for cell_id in cellIds] + return cellIds + if (zone_id_repr == "hexstring"): + raise ValueError("{__name__} doesn't support hexstring zone id representation") + + # the textual ZIRS of the nested index should be in the form of "{refinement level}_{nested index}". + def zone_id_to_textual(self, cellIds: List[Any], zone_id_repr: str, refinement_level=None) -> List[str]: + if (zone_id_repr not in get_args(ZoneIdRepresentationType)): + raise ValueError("{__name__} {zone_id_repr} representation is not supported.") + if (len(cellIds) == 0): + return [] + if (zone_id_repr == "textual"): + return cellIds + if (zone_id_repr == "int"): + # get_data return zone id in string format + return [str(refinement_level).zfill(2) + "_" + str(z) for z in cellIds] + if (zone_id_repr == "hexstring"): + raise ValueError("{__name__} doesn't support hexstring zone id representation") + + def get_cls_by_zone_level(self, zone_level: int) -> float: + return healpix_zone_statistics[zone_level]["cls"] + + def get_zone_level_by_cls(self, cls_km: float): + for k, v in healpix_zone_statistics.items(): + if (v["cls"] < cls_km * 1000): + return k + + # the textual ZIRS of the nested index should be in the form of "{refinement level}_{nested index}". + def get_cells_zone_level(self, cellIds: List[str]) -> List[int]: + refinement_level_str = list(map(lambda x: x.split("_")[0], cellIds)) + depth = [int(rf) for rf in refinement_level_str] + return depth + + def get_relative_zonelevels(self, cellId: str, base_level: int, zone_levels: List[int], + geometry: Optional[ReturnGeometryTypes] = 'zone-region') -> DGGRSProviderGetRelativeZoneLevelsReturn: + children = {} + geometry = geometry.lower() if (geometry is not None) else geometry + cellId = self.zone_id_from_textual([cellId], "int") + for z in zone_levels: + subzone_nestedIds = healpix_geo.nested.zoom_to(cellId, base_level, z) + subzones_geometry = None + if (geometry is not None): + subzones_geometry = [self._generateZoneGeometry(cellId, z, False if (geometry == 'zone-region') else True) + for cellId in subzone_nestedIds] + subzoneIds = self.zone_id_to_textual(subzone_nestedIds.tolist(), "int", z) + children[z] = DGGRSProviderZonesElement(**{'zoneIds': subzoneIds, + 'geometry': subzones_geometry}) + return DGGRSProviderGetRelativeZoneLevelsReturn(relative_zonelevels=children) + + def zonesinfo(self, cellIds: List[str]) -> DGGRSProviderZoneInfoReturn: + zone_level = self.get_cells_zone_level(cellIds)[0] + cellIds = self.zone_id_from_textual(cellIds, "int") + try: + centroids = [self._generateZoneGeometry(cellId, True) + for cellId in cellIds] + square_vertices = [self._generateZoneGeometry(cellId, False) + for cellId in cellIds] + extents = [shapely.from_geojson(json.dumps(geojson)) for geojson in square_vertices] + extents = [b.bounds for b in extents] + except Exception as e: + logger.error(f'{__name__} zone id {cellIds} convert failed, {e}') + raise Exception(f'{__name__} zone id {cellIds} convert failed, {e}') + return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'hexagon', + 'centroids': centroids, 'geometry': square_vertices, 'bbox': extents, + 'areaMetersSquare': self.mygrid.getRefZoneArea(zone_level)}) + + def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone: Union[str, int, None], + returngeometry: ReturnGeometryTypes, compact: bool = True) -> List[str]: + if (bbox is not None): + try: + bbox = shapely.bounds(bbox) + zones_list, _, _ = healpix_geo.nested.zone_coverage(tuple(bbox.tolist()), zone_level, ellipsoid=self.ellipsoid, flat=False) + zones_list = set(zones_list) + except Exception as e: + logger.error(f'{__name__} query zones list, bbox: {bbox} convert failed :{e}') + raise Exception(f"{__name__} query zones list, bbox: {bbox} convert failed {e}") + logger.info(f'{__name__} query zones list, number of cells: {len(zones_list)}') + if (parent_zone is not None): + try: + parent_zone_level = self.get_cells_zone_level([parent_zone])[0] + parent_zone = self.zone_id_from_textual([parent_zone], "int") + subzones_list = healpix_geo.nested.zoome_to(parent_zone, parent_zone_level, zone_level) + zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list + except Exception as e: + logger.error(f'{__name__} query zones list, parent_zone: {parent_zone} get children failed {e}') + raise Exception(f'parent_zone: {parent_zone} get children failed {e}') + if (len(zones_list) == 0): + raise Exception(f"{__name__} Parent zone {parent_zone} is not with in bbox: {bbox} at zone level {zone_level}") + # TODO: compact zones + zones_geometry = None + if (returngeometry is not None): + zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] + returnedAreaMetersSquare = [healpix_zone_statistics[zone_level]['area']] * len(zones_list) + zones_list = self.zone_id_to_textual(zones_list, "int", zone_level) + return DGGRSProviderZonesListReturn(**{'zones': zones_list, + 'geometry': zones_geometry, + 'returnedAreaMetersSquare': returnedAreaMetersSquare}) + + def _generateZoneGeometry(self, zoneId: int, refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.nested.healpix_to_lonlat(np.array([zoneId]), refinement_level, self.ellipsoid) + + return GeoJSONPoint(type="Point", coordinates=(lon[0][0], lat[0][1])) + else: + lon, lat = healpix_geo.nested.vertices([zoneId], refinement_level, self.ellipsoid) + vertices = np.squeeze(np.stack([lon, lat], axis=-1)) + coordinates = [] + for i in range(vertices.shape[0]): + coordinates.append((vertices[i][0], vertices[i][1])) + # to make the polygon a closed linestring + coordinates.append((vertices[0][0], vertices[0][1])) + return GeoJSONPolygon(type="Polygon", coordinates=[coordinates]) + + +class HealpixGeoZuniqProvider(AbstractDGGRSProvider): + def __init__(self, **params): + self.indexing_schema = params.get("indexing_schema", "zuniq").lower() + self.ellipsoid = params.get("ellipsoid", "wgs84").lower() + if (self.indexing_schema not in supported_indexing_schema): raise ValueError(f"{__name__} {self.indexing_schema} not supported") + if (self.ellipsoid not in supported_ellipsoid): + raise ValueError(f"{__name__} {self.ellipsoid} not supported") self.ellipsoid = self.ellipsoid.upper() def convert(self, zoneIds: List[str], targedggrs: str, @@ -167,14 +302,14 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone zones_list, _ = healpix_geo.zuniq.zone_coverage(tuple(bbox.tolist()), zone_level, ellipsoid=self.ellipsoid, flat=False) zones_list = set(zones_list) except Exception as e: - logger.error(f'{__name__} query zones list, bbox: {bbox} dggrid convert failed :{e}') - raise Exception(f"{__name__} query zones list, bbox: {bbox} dggrid convert failed {e}") - logger.info(f'{__name__} query zones list, number of hexagons: {len(zones_list)}') + logger.error(f'{__name__} query zones list, bbox: {bbox} convert failed :{e}') + raise Exception(f"{__name__} query zones list, bbox: {bbox} convert failed {e}") + logger.info(f'{__name__} query zones list, number of cells: {len(zones_list)}') if (parent_zone is not None): try: parent_zone_level = self.get_cells_zone_level([parent_zone])[0] parent_zone = self.zone_id_from_textual([parent_zone], "int") - parent_zone_nestedId, _ = healpix_geo.zuniq(parent_zone) + parent_zone_nestedId, _ = healpix_geo.zuniq.to_nested(parent_zone) subzones_list = healpix_geo.nested.zoome_to(parent_zone_nestedId, parent_zone_level, zone_level) subzones_list = healpix_geo.zuniq.from_nested(subzones_list, zone_level) zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list @@ -184,6 +319,8 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone if (len(zones_list) == 0): raise Exception(f"{__name__} Parent zone {parent_zone} is not with in bbox: {bbox} at zone level {zone_level}") # TODO: compact zones + if (returngeometry is not None): + zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] returnedAreaMetersSquare = [healpix_zone_statistics[zone_level]['area']] * len(zones_list) zones_list = self.zone_id_to_textual(zones_list, "int") From 5e3e8f073d85427165a61e7967851ace3f5d4001 Mon Sep 17 00:00:00 2001 From: Tik Date: Tue, 22 Sep 2026 17:25:46 +0300 Subject: [PATCH 05/10] 1. HealpixGeoNestedProvider 2. HealpixGeoZuniqProvider (support conversion to nested index) --- .../healpixgeo_dggrs_provider.py | 160 +++++++++--------- 1 file changed, 82 insertions(+), 78 deletions(-) diff --git a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py index 4792530..06fb1b9 100644 --- a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py +++ b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py @@ -1,4 +1,4 @@ -from pydggsapi.dependencies.dggrs_providers.abstract_dggrs_provider import AbstractDGGRSProvider, ZoneIdRepresentationType +from pydggsapi.dependencies.dggrs_providers.abstract_dggrs_provider import AbstractDGGRSProvider, ZoneIdRepresentationType, conversion_properties from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint from pydggsapi.schemas.api.dggrs_providers import ( DGGRSProviderZoneInfoReturn, @@ -64,10 +64,8 @@ class HealpixGeoNestedProvider(AbstractDGGRSProvider): def __init__(self, **params): - self.indexing_schema = params.get("indexing_schema", "nested").lower() + self.indexing_schema = "nested" self.ellipsoid = params.get("ellipsoid", "wgs84").lower() - if (self.indexing_schema not in supported_indexing_schema): - raise ValueError(f"{__name__} {self.indexing_schema} not supported") if (self.ellipsoid not in supported_ellipsoid): raise ValueError(f"{__name__} {self.ellipsoid} not supported") self.ellipsoid = self.ellipsoid.upper() @@ -101,7 +99,8 @@ def zone_id_to_textual(self, cellIds: List[Any], zone_id_repr: str, refinement_l return cellIds if (zone_id_repr == "int"): # get_data return zone id in string format - return [str(refinement_level).zfill(2) + "_" + str(z) for z in cellIds] + cellIds = list(map(lambda x: str(refinement_level).zfill(2) + "_" + str(x), cellIds)) + return cellIds if (zone_id_repr == "hexstring"): raise ValueError("{__name__} doesn't support hexstring zone id representation") @@ -125,12 +124,13 @@ def get_relative_zonelevels(self, cellId: str, base_level: int, zone_levels: Lis geometry = geometry.lower() if (geometry is not None) else geometry cellId = self.zone_id_from_textual([cellId], "int") for z in zone_levels: - subzone_nestedIds = healpix_geo.nested.zoom_to(cellId, base_level, z) + # it return an N x childen array where N is the number of parents + # so we get the only element from the return as the get_relative_zonelevels is for a single cell. + subzoneIds = healpix_geo.nested.zoom_to(cellId, base_level, z)[0] subzones_geometry = None if (geometry is not None): - subzones_geometry = [self._generateZoneGeometry(cellId, z, False if (geometry == 'zone-region') else True) - for cellId in subzone_nestedIds] - subzoneIds = self.zone_id_to_textual(subzone_nestedIds.tolist(), "int", z) + subzones_geometry = self._generateZoneGeometries(subzoneIds, z, False if (geometry == 'zone-region') else True) + subzoneIds = self.zone_id_to_textual(subzoneIds.tolist(), "int", z) children[z] = DGGRSProviderZonesElement(**{'zoneIds': subzoneIds, 'geometry': subzones_geometry}) return DGGRSProviderGetRelativeZoneLevelsReturn(relative_zonelevels=children) @@ -139,18 +139,16 @@ def zonesinfo(self, cellIds: List[str]) -> DGGRSProviderZoneInfoReturn: zone_level = self.get_cells_zone_level(cellIds)[0] cellIds = self.zone_id_from_textual(cellIds, "int") try: - centroids = [self._generateZoneGeometry(cellId, True) - for cellId in cellIds] - square_vertices = [self._generateZoneGeometry(cellId, False) - for cellId in cellIds] - extents = [shapely.from_geojson(json.dumps(geojson)) for geojson in square_vertices] + centroids = self._generateZoneGeometries([cellIds], zone_level, True) + square_vertices = self._generateZoneGeometries([cellIds], zone_level, False) + extents = [shapely.geometry.shape(geojson.__dict__) for geojson in square_vertices] extents = [b.bounds for b in extents] except Exception as e: logger.error(f'{__name__} zone id {cellIds} convert failed, {e}') raise Exception(f'{__name__} zone id {cellIds} convert failed, {e}') - return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'hexagon', + return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'rhombus', 'centroids': centroids, 'geometry': square_vertices, 'bbox': extents, - 'areaMetersSquare': self.mygrid.getRefZoneArea(zone_level)}) + 'areaMetersSquare': zone_level}) def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone: Union[str, int, None], returngeometry: ReturnGeometryTypes, compact: bool = True) -> List[str]: @@ -177,42 +175,58 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone # TODO: compact zones zones_geometry = None if (returngeometry is not None): - zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] + zones_geometry = self._generateZoneGeometries(list(zones_list), zone_level, False if (returngeometry == 'zone-region') else True) returnedAreaMetersSquare = [healpix_zone_statistics[zone_level]['area']] * len(zones_list) zones_list = self.zone_id_to_textual(zones_list, "int", zone_level) return DGGRSProviderZonesListReturn(**{'zones': zones_list, 'geometry': zones_geometry, 'returnedAreaMetersSquare': returnedAreaMetersSquare}) - def _generateZoneGeometry(self, zoneId: int, refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + def _generateZoneGeometries(self, zoneIds: List[int], refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: if centroids: - lon, lat = healpix_geo.nested.healpix_to_lonlat(np.array([zoneId]), refinement_level, self.ellipsoid) - - return GeoJSONPoint(type="Point", coordinates=(lon[0][0], lat[0][1])) + lon, lat = healpix_geo.nested.healpix_to_lonlat(zoneIds, refinement_level, self.ellipsoid) + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points else: - lon, lat = healpix_geo.nested.vertices([zoneId], refinement_level, self.ellipsoid) - vertices = np.squeeze(np.stack([lon, lat], axis=-1)) - coordinates = [] - for i in range(vertices.shape[0]): - coordinates.append((vertices[i][0], vertices[i][1])) - # to make the polygon a closed linestring - coordinates.append((vertices[0][0], vertices[0][1])) - return GeoJSONPolygon(type="Polygon", coordinates=[coordinates]) + lon, lat = healpix_geo.nested.vertices(zoneIds, refinement_level, self.ellipsoid) + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon class HealpixGeoZuniqProvider(AbstractDGGRSProvider): def __init__(self, **params): - self.indexing_schema = params.get("indexing_schema", "zuniq").lower() + self.indexing_schema = "zuniq" self.ellipsoid = params.get("ellipsoid", "wgs84").lower() - if (self.indexing_schema not in supported_indexing_schema): - raise ValueError(f"{__name__} {self.indexing_schema} not supported") if (self.ellipsoid not in supported_ellipsoid): raise ValueError(f"{__name__} {self.ellipsoid} not supported") self.ellipsoid = self.ellipsoid.upper() + nested_conversion_properties = conversion_properties(zonelevel_offset=0) + self.dggrs_conversion = {"nested": nested_conversion_properties} - def convert(self, zoneIds: List[str], targedggrs: str, + def convert(self, zoneIds: List[str], targetdggrs: str, zone_id_repr: ZoneIdRepresentationType = 'textual') -> DGGRSProviderConversionReturn: - raise NotImplementedError + match targetdggrs: + + case "nested": + # in the case of healpix, conversion from zuniq index to nested is a 1-to-1 mapping. + nestedIds, refinement_level = healpix_geo.zuniq.to_nested(zoneIds) + nestedIds, refinement_level = nestedIds.tolist(), refinement_level.tolist() + if (zone_id_repr == 'textual'): + nestedIds = list(map(lambda x: f"{refinement_level[x]}_{nestedIds[x]}", + range(len(nestedIds)))) + return DGGRSProviderConversionReturn(zoneIds=zoneIds, target_zoneIds=nestedIds, + target_res=refinement_level) + case _: + raise Exception(f"{__name__} conversion to {targetdggrs} not supported.") # at the time of implementation, healpix_geo doesn't support textural repr of zone id # so assume the input is just uint64 in str @@ -265,14 +279,18 @@ def get_relative_zonelevels(self, cellId: str, base_level: int, zone_levels: Lis geometry = geometry.lower() if (geometry is not None) else geometry cellId = self.zone_id_from_textual([cellId], "int") # utilising the zoom_to function of nested index - nested_cellId, nested_rf = healpix_geo.zuniq.to_nested(np.array(cellId)) + nested_cellId, nested_rf = healpix_geo.zuniq.to_nested(cellId) + nested_cellId, nested_rf = nested_cellId[0], nested_rf[0] for z in zone_levels: - subzone_nestedIds = healpix_geo.nested.zoom_to(nested_cellId, nested_rf, z) + # it return an N x childen array where N is the number of parents + # so we get the only element from the return as the get_relative_zonelevels is for a single cell. + subzone_nestedIds = healpix_geo.nested.zoom_to(nested_cellId, nested_rf, z)[0] subzones_geometry = None + # convert those subzone cell id from nested back to zuniq + subzoneIds = healpix_geo.nested.to_zuniq(subzone_nestedIds, z) if (geometry is not None): - subzones_geometry = [self._nestedindex_generateZoneGeometry(cellId, z, False if (geometry == 'zone-region') else True) - for cellId in subzone_nestedIds] - subzoneIds = self.zone_id_to_textual(subzone_nestedIds.tolist(), "int") + subzones_geometry = self._generateZoneGeometries(subzoneIds, False if (geometry == 'zone-region') else True) + subzoneIds = self.zone_id_to_textual(subzoneIds.tolist(), "int") children[z] = DGGRSProviderZonesElement(**{'zoneIds': subzoneIds, 'geometry': subzones_geometry}) return DGGRSProviderGetRelativeZoneLevelsReturn(relative_zonelevels=children) @@ -281,18 +299,16 @@ def zonesinfo(self, cellIds: List[str]) -> DGGRSProviderZoneInfoReturn: zone_level = self.get_cells_zone_level(cellIds)[0] cellIds = self.zone_id_from_textual(cellIds, "int") try: - centroids = [self._generateZoneGeometry(cellId, True) - for cellId in cellIds] - square_vertices = [self._generateZoneGeometry(cellId, False) - for cellId in cellIds] - extents = [shapely.from_geojson(json.dumps(geojson)) for geojson in square_vertices] + centroids = self._generateZoneGeometries([cellIds], True) + square_vertices = self._generateZoneGeometries([cellIds], False) + extents = [shapely.geometry.shape(geojson.__dict__) for geojson in square_vertices] extents = [b.bounds for b in extents] except Exception as e: logger.error(f'{__name__} zone id {cellIds} convert failed, {e}') raise Exception(f'{__name__} zone id {cellIds} convert failed, {e}') - return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'hexagon', + return DGGRSProviderZoneInfoReturn(**{'zone_level': zone_level, 'shapeType': 'rhombus', 'centroids': centroids, 'geometry': square_vertices, 'bbox': extents, - 'areaMetersSquare': self.mygrid.getRefZoneArea(zone_level)}) + 'areaMetersSquare': zone_level}) def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone: Union[str, int, None], returngeometry: ReturnGeometryTypes, compact: bool = True) -> List[str]: @@ -309,9 +325,7 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone try: parent_zone_level = self.get_cells_zone_level([parent_zone])[0] parent_zone = self.zone_id_from_textual([parent_zone], "int") - parent_zone_nestedId, _ = healpix_geo.zuniq.to_nested(parent_zone) - subzones_list = healpix_geo.nested.zoome_to(parent_zone_nestedId, parent_zone_level, zone_level) - subzones_list = healpix_geo.zuniq.from_nested(subzones_list, zone_level) + subzones_list = healpix_geo.nested.zoome_to(parent_zone, parent_zone_level, zone_level) zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list except Exception as e: logger.error(f'{__name__} query zones list, parent_zone: {parent_zone} get children failed {e}') @@ -319,41 +333,31 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone if (len(zones_list) == 0): raise Exception(f"{__name__} Parent zone {parent_zone} is not with in bbox: {bbox} at zone level {zone_level}") # TODO: compact zones + zones_geometry = None if (returngeometry is not None): - zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] - zones_geometry = [self._generateZoneGeometry(z, False if (returngeometry == 'zone-region') else True) for z in zones_list] + zones_geometry = self._generateZoneGeometries(list(zones_list), False if (returngeometry == 'zone-region') else True) returnedAreaMetersSquare = [healpix_zone_statistics[zone_level]['area']] * len(zones_list) - zones_list = self.zone_id_to_textual(zones_list, "int") + zones_list = self.zone_id_to_textual(zones_list, "int", zone_level) return DGGRSProviderZonesListReturn(**{'zones': zones_list, 'geometry': zones_geometry, 'returnedAreaMetersSquare': returnedAreaMetersSquare}) - def _nestedindex_generateZoneGeometry(self, zoneId: int, refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: - if centroids: - lon, lat = healpix_geo.nested.healpix_to_lonlat(np.array([zoneId]), refinement_level, self.ellipsoid) - - return GeoJSONPoint(type="Point", coordinates=(lon[0][0], lat[0][1])) - else: - lon, lat = healpix_geo.nested.vertices([zoneId], refinement_level, self.ellipsoid) - vertices = np.squeeze(np.stack([lon, lat], axis=-1)) - coordinates = [] - for i in range(vertices.shape[0]): - coordinates.append((vertices[i][0], vertices[i][1])) - # to make the polygon a closed linestring - coordinates.append((vertices[0][0], vertices[0][1])) - return GeoJSONPolygon(type="Polygon", coordinates=[coordinates]) - - def _generateZoneGeometry(self, zoneId: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + def _generateZoneGeometries(self, zoneIds: List[int], centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: if centroids: - lon, lat = healpix_geo.zuniq.healpix_to_lonlat(np.array([zoneId]), self.ellipsoid) - return GeoJSONPoint(type="Point", coordinates=(lon[0][0], lat[0][1])) + lon, lat = healpix_geo.zuniq.healpix_to_lonlat(zoneIds, self.ellipsoid) + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points else: - lon, lat = healpix_geo.zuniq.vertices([zoneId], self.ellipsoid) - vertices = np.squeeze(np.stack([lon, lat], axis=-1)) - coordinates = [] - for i in range(vertices.shape[0]): - coordinates.append((vertices[i][0], vertices[i][1])) - # to make the polygon a closed linestring - coordinates.append((vertices[0][0], vertices[0][1])) - return GeoJSONPolygon(type="Polygon", coordinates=[coordinates]) + lon, lat = healpix_geo.zuniq.vertices(zoneIds, self.ellipsoid) + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon From 0d025aa86877d4c75082c0cdb4542cb03d3e553c Mon Sep 17 00:00:00 2001 From: Tik Date: Tue, 22 Sep 2026 17:28:02 +0300 Subject: [PATCH 06/10] 1. ZarrCollectionProvider: xarray query to use the dimension as the key. 2. tiles api: support conversion to other dggrsId is convert if supported. 3. changed to use shapely.geometry.shape instead of from_geojson 4. --- pydggsapi/dependencies/api/mercator.py | 8 ++++--- .../zarr_collection_provider.py | 6 ++--- pydggsapi/models/ogc_dggs/data_retrieval.py | 2 +- pydggsapi/routers/tiles_api.py | 22 ++++++++++++++++++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pydggsapi/dependencies/api/mercator.py b/pydggsapi/dependencies/api/mercator.py index 28ea89d..8b7236b 100644 --- a/pydggsapi/dependencies/api/mercator.py +++ b/pydggsapi/dependencies/api/mercator.py @@ -35,15 +35,17 @@ def __init__(self): 17: {"Tiles": 17179869184, "Tile width deg lons": 0.00275, "m per pixel": 1.194}, 18: {"Tiles": 68719476736, "Tile width deg lons": 0.001375, "m per pixel": 0.597}, 19: {"Tiles": 274877906944, "Tile width deg lons": 0.0006875, "m per pixel": 0.299}, - 20: {"Tiles": 1099511627776, "Tile width deg lons": 0.00034375, "m per pixel": 0.149} + 20: {"Tiles": 1099511627776, "Tile width deg lons": 0.00034375, "m per pixel": 0.149}, + 21: {"Tiles": 4398046511104, "Tile width deg lons": 0.000171661, "m per pixel": 0.07}, + 22: {"Tiles": 17592186044416, "Tile width deg lons": 0.000085831, "m per pixel": 0.03}, } def get(self, zoom): # zoom must be integer and between 0 and 20 inclusive if not isinstance(zoom, int): raise TypeError("zoom must be integer") - if zoom < 0 or zoom > 20: - raise ValueError("zoom must be between 0 and 20 inclusive") + if zoom < 0 or zoom > 22: + raise ValueError("zoom must be between 0 and 22 inclusive") return self.zoom_info[zoom] diff --git a/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py b/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py index ca54b0c..17b4fd1 100644 --- a/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py +++ b/pydggsapi/dependencies/collections_providers/zarr_collection_provider.py @@ -113,10 +113,8 @@ def get_data(self, zoneIds: List[Any], res: int, datasource_id: str, else: cols = OrderedSet(ds.data_vars) if ("*" in datasource.data_cols) else OrderedSet(datasource.data_cols) cols = list(cols - OrderedSet(datasource.exclude_data_cols)) - # fuzzy selection to reduce the dataset size - zarr_result = ds.sel({id_col: zoneIds}, method='pad').drop_duplicates(id_col) - # second selection to make it exact - zarr_result = zarr_result.query({id_col: f'{id_col} in {zoneIds}'}) + dimension = ds[id_col].dims[0] + zarr_result = ds.query({dimension: f'{id_col} in {zoneIds}'}) zarr_result = zarr_result[cols] except Exception as e: # Zarr will raise exception if nothing matched diff --git a/pydggsapi/models/ogc_dggs/data_retrieval.py b/pydggsapi/models/ogc_dggs/data_retrieval.py index 6ab5361..6e8c84a 100644 --- a/pydggsapi/models/ogc_dggs/data_retrieval.py +++ b/pydggsapi/models/ogc_dggs/data_retrieval.py @@ -97,7 +97,7 @@ def query_zone_data( # get data for all relative_levels for the currnet datasource for z, v in result.relative_zonelevels.items(): - g = [shapely.from_geojson(json.dumps(g.__dict__))for g in v.geometry] if (returngeometry is not None) else None + g = [shapely.geometry.shape(g.__dict__) for g in v.geometry] if (returngeometry is not None) else None converted_z = z if (convert): # convert the source dggrs ID to the datasource dggrs zoneID. diff --git a/pydggsapi/routers/tiles_api.py b/pydggsapi/routers/tiles_api.py index 839346c..c558089 100644 --- a/pydggsapi/routers/tiles_api.py +++ b/pydggsapi/routers/tiles_api.py @@ -57,11 +57,13 @@ async def query_mvt_tiles( collection = collection_info[tilesreq.collectionId] collection_provider = _get_collection_provider(collection.collection_provider.providerId)[collection.collection_provider.providerId] ds = collection_provider.datasources[collection.collection_provider.datasource_id] + conversion = True if (tilesreq.dggrsId != collection.collection_provider.dggrsId) else False id_col = getattr(ds, "id_col", "zone_id") if (id_col == ''): id_col = "zone_id" bbox, tile = mercator.getWGS84bbox(tilesreq.z, tilesreq.x, tilesreq.y) res_info = mercator.get(tile.z) + print(tile.z) tile_width_km = float(res_info["Tile width deg lons"]) / 0.01 * 0.4 # in tile_width_km zone_level = dggrs_provider.get_zone_level_by_cls(tile_width_km) if (tilesreq.relative_depth != 0): @@ -77,10 +79,23 @@ async def query_mvt_tiles( return Response(bytes(content), media_type="application/x-protobuf") logger.debug(f'{__name__} zone level:{zone_level}, tile width:{tile_width_km}, bbox:{bbox}') zoneslist = dggrs_provider.zoneslist(clip_bound, zone_level, parent_zone=None, returngeometry='zone-region', compact=False) - geometry = [shapely.from_geojson(json.dumps(g.__dict__)) for g in zoneslist.geometry] + geometry = [shapely.geometry.shape(g.__dict__) for g in zoneslist.geometry] if (collection.collection_provider.dggrs_zoneid_repr != "textual"): zoneslist.zones = dggrs_provider.zone_id_from_textual(zoneslist.zones, collection.collection_provider.dggrs_zoneid_repr) zoneslist = gpd.GeoDataFrame({'zone_id': zoneslist.zones}, geometry=geometry).set_index('zone_id') + if (conversion): + targetdggrs = collection.collection_provider.dggrsId + if (targetdggrs in list(dggrs_provider.dggrs_conversion.keys())): + targetdggrsprovider = _get_dggrs_provider(targetdggrs) + converted = dggrs_provider.convert(zoneslist.index.to_list(), targetdggrs) + if (collection.collection_provider.dggrs_zoneid_repr != "textual"): + converted.target_zoneIds = targetdggrsprovider.zone_id_from_textual(converted.target_zoneIds, + collection.collection_provider.dggrs_zoneid_repr) + # zone_id == collection zone ids , target_id = incomming zone id from request + # swapped in comparison to no conversion is needed. + convertedlist = gpd.GeoDataFrame({'zone_id': converted.target_zoneIds, + 'target_id': converted.zoneIds}).set_index('target_id') + zoneslist = convertedlist.join(zoneslist).reset_index().set_index('zone_id') zones_data = collection_provider.get_data(zoneslist.index.to_list(), zone_level, collection.collection_provider.datasource_id, input_zoneIds_padding=False) if (len(zones_data.zoneIds) == 0): @@ -99,6 +114,11 @@ async def query_mvt_tiles( zones_data = gpd.GeoDataFrame(zones_data.data, index=pd_indexes, columns=list(zones_data.cols_meta.keys())) zones_data = zones_data.join(zoneslist).reset_index(names=indexes_cols) zones_data[id_col] = zones_data[id_col].astype(zoneslist.index.dtype) + if (conversion): + zones_geometry = zones_data.groupby("target_id")["geometry"].first() + zones_data = zones_data.drop(columns="geometry").groupby('target_id').mean() + zones_data = zones_data.drop(columns=indexes_cols) + zones_data = zones_data.join(zones_geometry).reset_index(names=indexes_cols) if (collection.collection_provider.dggrs_zoneid_repr != 'textual'): zones_data[id_col] = dggrs_provider.zone_id_to_textual(zones_data[id_col].values, collection.collection_provider.dggrs_zoneid_repr, zone_level) From 97e71c63ac8ac81f93c4bdcbdd435d6a8eefbd48 Mon Sep 17 00:00:00 2001 From: Tik Date: Wed, 23 Sep 2026 20:45:15 +0300 Subject: [PATCH 07/10] - added test cases for healpixgeo dggrs provider - added testing data for healpixgeo - bugfix healpixgeo dggrs provider --- poetry.lock | 50 +- .../healpixgeo_dggrs_provider.py | 9 +- pyproject.toml | 2 +- tests/dggs_api_config_testing.json | 84 ++ ...test_cql_data-retrieval_query_zone_data.py | 14 +- .../test_cql_zone-query_dggrs_zones.py | 13 +- .../test_temporal_zone-query_dggrs_zones.py | 4 +- .../refinement_level_10/cell_ids/c/0 | Bin 0 -> 119 bytes .../refinement_level_10/cell_ids/zarr.json | 48 + .../refinement_level_10/twi/c/0 | Bin 0 -> 124 bytes .../refinement_level_10/twi/zarr.json | 46 + .../refinement_level_10/zarr.json | 5 + .../refinement_level_11/cell_ids/c/0 | Bin 0 -> 131 bytes .../refinement_level_11/cell_ids/zarr.json | 48 + .../refinement_level_11/twi/c/0 | Bin 0 -> 251 bytes .../refinement_level_11/twi/zarr.json | 46 + .../refinement_level_11/zarr.json | 5 + .../refinement_level_12/cell_ids/c/0 | Bin 0 -> 213 bytes .../refinement_level_12/cell_ids/zarr.json | 48 + .../refinement_level_12/twi/c/0 | Bin 0 -> 586 bytes .../refinement_level_12/twi/zarr.json | 46 + .../refinement_level_12/zarr.json | 5 + .../refinement_level_13/cell_ids/c/0 | Bin 0 -> 321 bytes .../refinement_level_13/cell_ids/zarr.json | 48 + .../refinement_level_13/twi/c/0 | Bin 0 -> 1753 bytes .../refinement_level_13/twi/zarr.json | 46 + .../refinement_level_13/zarr.json | 5 + .../refinement_level_14/cell_ids/c/0 | Bin 0 -> 713 bytes .../refinement_level_14/cell_ids/zarr.json | 48 + .../refinement_level_14/twi/c/0 | Bin 0 -> 5937 bytes .../refinement_level_14/twi/zarr.json | 46 + .../refinement_level_14/zarr.json | 5 + .../refinement_level_5/cell_ids/c/0 | Bin 0 -> 88 bytes .../refinement_level_5/cell_ids/zarr.json | 48 + .../refinement_level_5/twi/zarr.json | 46 + .../refinement_level_5/zarr.json | 5 + .../refinement_level_6/cell_ids/c/0 | Bin 0 -> 104 bytes .../refinement_level_6/cell_ids/zarr.json | 48 + .../refinement_level_6/twi/zarr.json | 46 + .../refinement_level_6/zarr.json | 5 + .../refinement_level_7/cell_ids/c/0 | Bin 0 -> 96 bytes .../refinement_level_7/cell_ids/zarr.json | 48 + .../refinement_level_7/twi/zarr.json | 46 + .../refinement_level_7/zarr.json | 5 + .../refinement_level_8/cell_ids/c/0 | Bin 0 -> 120 bytes .../refinement_level_8/cell_ids/zarr.json | 48 + .../refinement_level_8/twi/c/0 | Bin 0 -> 120 bytes .../refinement_level_8/twi/zarr.json | 46 + .../refinement_level_8/zarr.json | 5 + .../refinement_level_9/cell_ids/c/0 | Bin 0 -> 82 bytes .../refinement_level_9/cell_ids/zarr.json | 48 + .../refinement_level_9/twi/c/0 | Bin 0 -> 85 bytes .../refinement_level_9/twi/zarr.json | 46 + .../refinement_level_9/zarr.json | 5 + .../zarr.json | 1051 +++++++++++++++++ .../refinement_level_10/cell_ids/c/0 | Bin 0 -> 113 bytes .../refinement_level_10/cell_ids/zarr.json | 48 + .../refinement_level_10/twi/c/0 | Bin 0 -> 124 bytes .../refinement_level_10/twi/zarr.json | 46 + .../refinement_level_10/zarr.json | 5 + .../refinement_level_11/cell_ids/c/0 | Bin 0 -> 140 bytes .../refinement_level_11/cell_ids/zarr.json | 48 + .../refinement_level_11/twi/c/0 | Bin 0 -> 251 bytes .../refinement_level_11/twi/zarr.json | 46 + .../refinement_level_11/zarr.json | 5 + .../refinement_level_12/cell_ids/c/0 | Bin 0 -> 210 bytes .../refinement_level_12/cell_ids/zarr.json | 48 + .../refinement_level_12/twi/c/0 | Bin 0 -> 586 bytes .../refinement_level_12/twi/zarr.json | 46 + .../refinement_level_12/zarr.json | 5 + .../refinement_level_13/cell_ids/c/0 | Bin 0 -> 329 bytes .../refinement_level_13/cell_ids/zarr.json | 48 + .../refinement_level_13/twi/c/0 | Bin 0 -> 1753 bytes .../refinement_level_13/twi/zarr.json | 46 + .../refinement_level_13/zarr.json | 5 + .../refinement_level_14/cell_ids/c/0 | Bin 0 -> 719 bytes .../refinement_level_14/cell_ids/zarr.json | 48 + .../refinement_level_14/twi/c/0 | Bin 0 -> 5937 bytes .../refinement_level_14/twi/zarr.json | 46 + .../refinement_level_14/zarr.json | 5 + .../refinement_level_5/cell_ids/c/0 | Bin 0 -> 88 bytes .../refinement_level_5/cell_ids/zarr.json | 48 + .../refinement_level_5/twi/zarr.json | 46 + .../refinement_level_5/zarr.json | 5 + .../refinement_level_6/cell_ids/c/0 | Bin 0 -> 104 bytes .../refinement_level_6/cell_ids/zarr.json | 48 + .../refinement_level_6/twi/zarr.json | 46 + .../refinement_level_6/zarr.json | 5 + .../refinement_level_7/cell_ids/c/0 | Bin 0 -> 96 bytes .../refinement_level_7/cell_ids/zarr.json | 48 + .../refinement_level_7/twi/zarr.json | 46 + .../refinement_level_7/zarr.json | 5 + .../refinement_level_8/cell_ids/c/0 | Bin 0 -> 120 bytes .../refinement_level_8/cell_ids/zarr.json | 48 + .../refinement_level_8/twi/c/0 | Bin 0 -> 120 bytes .../refinement_level_8/twi/zarr.json | 46 + .../refinement_level_8/zarr.json | 5 + .../refinement_level_9/cell_ids/c/0 | Bin 0 -> 81 bytes .../refinement_level_9/cell_ids/zarr.json | 48 + .../refinement_level_9/twi/c/0 | Bin 0 -> 85 bytes .../refinement_level_9/twi/zarr.json | 46 + .../refinement_level_9/zarr.json | 5 + .../zarr.json | 1051 +++++++++++++++++ 103 files changed, 4244 insertions(+), 14 deletions(-) create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_5/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_5/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_5/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_5/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_6/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_6/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_6/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_6/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/cell_ids/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/cell_ids/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/twi/c/0 create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/twi/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/zarr.json create mode 100644 tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/zarr.json diff --git a/poetry.lock b/poetry.lock index 3f09abb..b3c7a67 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2594,6 +2594,38 @@ files = [ [package.dependencies] numpy = ">=1.21.2" +[[package]] +name = "healpix-geo" +version = "0.4.1" +description = "" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "healpix_geo-0.4.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4b9a864e185d16cd2a83116aeda7aa2c939f2f664a00d42f0734d57976f70c19"}, + {file = "healpix_geo-0.4.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:079089fae20317094b16fbb42b353f01b3b3ea4b44fa4e72a776d3fbbf0e573d"}, + {file = "healpix_geo-0.4.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:265c845c32d1643bcff6b84732bc17a90f1007da5e1e16cb509db8a41a059e8f"}, + {file = "healpix_geo-0.4.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34dde8858cc67ebcd3a85489e6ccdda0e045ccf4ed0c4f496fe0f90aed092318"}, + {file = "healpix_geo-0.4.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e4e5de294bfaf7de38a72e7a16b030916be1bfd8896ebbb60c7c548e1cd2bac"}, + {file = "healpix_geo-0.4.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7036cdac9d17c1185c12bf9e96243c27012d95c545059807874c3d13cb4296c"}, + {file = "healpix_geo-0.4.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a88fdf30256b6ed27e40ddc5998b10db43d98003887f39b978041f4d2db812cd"}, + {file = "healpix_geo-0.4.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcd6d1db979d7d4d76af106378e6acac254248d0377e6eee13a08853b39b342b"}, + {file = "healpix_geo-0.4.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7054692109202bcdd1b7a0d94d3f472b64470422432747358028eb43a5bae82f"}, + {file = "healpix_geo-0.4.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:464f8558e497f6e3252363d5b0a9f351c72d1c9f51e0422351cd06cfc61e1147"}, + {file = "healpix_geo-0.4.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:f29a39cd1bb350fd154a51cff1194627fbfffa3d2bf5e8bf4d5340c4910c3d92"}, + {file = "healpix_geo-0.4.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:58a5b29f0e3814e13d7e20a1bea9cddd317f77a1e3851b4f662dd46725b042e5"}, + {file = "healpix_geo-0.4.1-cp310-abi3-win32.whl", hash = "sha256:db882c3b6bc73e0f6b05b2318a3099b88dc50311e87c03aaf7d3f46a01f21e9d"}, + {file = "healpix_geo-0.4.1-cp310-abi3-win_amd64.whl", hash = "sha256:bf1fe47623bba13845f5a2a8782a8d808b27e9afd46cf6c9053c86f4ec916cb8"}, +] + +[package.dependencies] +marray = "*" +numpy = ">=2.0" + +[package.extras] +geometries = ["shapely (>=2)"] +ragged = ["awkward", "ragged"] + [[package]] name = "httpcore" version = "1.0.9" @@ -3470,6 +3502,22 @@ files = [ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] +[[package]] +name = "marray" +version = "0.0.12" +description = "Masked versions of array API compatible arrays" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "marray-0.0.12-py3-none-any.whl", hash = "sha256:86e1bf6120e9eb39ab3c4b4dd9ae8283f8f92d5c2895c0fc0f56c6255c50ecb7"}, + {file = "marray-0.0.12.tar.gz", hash = "sha256:d9cb475b0dee78ecb0f56cf0e7003d2bdb7d060ae935454e833823767c08d2bc"}, +] + +[package.extras] +docs = ["ghp-import", "jupyter-book"] +test = ["array-api-compat", "array-api-strict", "numpy", "pytest", "pytest-cov", "pytorch"] + [[package]] name = "matplotlib-inline" version = "0.2.1" @@ -7299,4 +7347,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.14" -content-hash = "33db9e17a178bbddff4070d90a984265bf54b1877ac13f414d683e7a32f1c076" +content-hash = "e133756cd8df46fed6bcccf3bde551f0991cbd148670d7c963f7211394b8386d" diff --git a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py index 06fb1b9..1fdf6e2 100644 --- a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py +++ b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py @@ -165,7 +165,7 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone try: parent_zone_level = self.get_cells_zone_level([parent_zone])[0] parent_zone = self.zone_id_from_textual([parent_zone], "int") - subzones_list = healpix_geo.nested.zoome_to(parent_zone, parent_zone_level, zone_level) + subzones_list = set(healpix_geo.nested.zoom_to(parent_zone, parent_zone_level, zone_level)[0]) zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list except Exception as e: logger.error(f'{__name__} query zones list, parent_zone: {parent_zone} get children failed {e}') @@ -323,9 +323,10 @@ def zoneslist(self, bbox: Union[shapely.box, None], zone_level: int, parent_zone logger.info(f'{__name__} query zones list, number of cells: {len(zones_list)}') if (parent_zone is not None): try: - parent_zone_level = self.get_cells_zone_level([parent_zone])[0] - parent_zone = self.zone_id_from_textual([parent_zone], "int") - subzones_list = healpix_geo.nested.zoome_to(parent_zone, parent_zone_level, zone_level) + parent_zone_nested, parent_zone_level = healpix_geo.zuniq.to_nested(parent_zone) + subzones_list = healpix_geo.nested.zoom_to(parent_zone_nested, + parent_zone_level[0], zone_level)[0] + subzones_list = set(healpix_geo.nested.to_zuniq(subzones_list, zone_level)) zones_list = (zones_list & subzones_list) if (bbox is not None) else subzones_list except Exception as e: logger.error(f'{__name__} query zones list, parent_zone: {parent_zone} get children failed {e}') diff --git a/pyproject.toml b/pyproject.toml index e1bfae7..9e313a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ pytest-env = "^1.6.0" zarr = "^3.2.1" s3fs = ">=2026.2.0,<2027.0.0" gcsfs = ">=2026.2.0,<2027.0.0" -healpix-geo = "^0.1.2" +healpix-geo = "0.4.1" [tool.poetry.group.dev.dependencies] diff --git a/tests/dggs_api_config_testing.json b/tests/dggs_api_config_testing.json index e6d0134..10eb4de 100644 --- a/tests/dggs_api_config_testing.json +++ b/tests/dggs_api_config_testing.json @@ -59,6 +59,36 @@ "max_refinement_level": 14, "datasource_id": "est_topo_slope_10m_elva_healpix_temporal" } + } + }, + "5": { + "Estonia_twi_10m_elva_healpixgeo_nested_zarr": { + "title": "The Slope dataset of Estonia near Elva in HEALPix(nested) DGGRS stored in Zarr format", + "description": "A HEALPix dataset converted from a clipped TWI near Elva with 10M spatial resolution. The dataset", + "extent": {"spatial": {"bbox": [[26.2718, 58.2267, 26.5815, 58.3295]]}}, + "collection_provider": { + "providerId": "zarr", + "dggrsId": "healpixgeo_nested", + "dggrs_zoneid_repr": "int", + "min_refinement_level": 5, + "max_refinement_level": 14, + "datasource_id": "est_topo_twi_10m_elva_healpix_nested" + } + } + }, + "6": { + "Estonia_twi_10m_elva_healpixgeo_zuniq_zarr": { + "title": "The Slope dataset of Estonia near Elva in HEALPix(zuniq) DGGRS stored in Zarr format", + "description": "A HEALPix dataset converted from a clipped TWI near Elva with 10M spatial resolution. The dataset", + "extent": {"spatial": {"bbox": [[26.2718, 58.2267, 26.5815, 58.3295]]}}, + "collection_provider": { + "providerId": "zarr", + "dggrsId": "healpixgeo_zuniq", + "dggrs_zoneid_repr": "int", + "min_refinement_level": 5, + "max_refinement_level": 14, + "datasource_id": "est_topo_twi_10m_elva_healpix_zuniq" + } } } }, @@ -96,6 +126,28 @@ "classname": "dggal_dggrs_provider.DGGALProvider", "parameters": {"grid": "HEALPIX"} } + }, + "4": { + "healpixgeo_nested": { + "title": "HEALPix using healpix-geo with nested indexing", + "description": "desc", + "crs": "wgs84", + "shapeType": "rhombus", + "definition_link": "https://healpix-geo.readthedocs.io/en/latest/healpix/index.html", + "defaultDepth": 1, + "classname": "healpixgeo_dggrs_provider.HealpixGeoNestedProvider" + } + }, + "5": { + "healpixgeo_zuniq": { + "title": "HEALPix using healpix-geo with zuniq indexing", + "description": "desc", + "crs": "wgs84", + "shapeType": "rhombus", + "definition_link": "https://healpix-geo.readthedocs.io/en/latest/healpix/index.html", + "defaultDepth": 1, + "classname": "healpixgeo_dggrs_provider.HealpixGeoZuniqProvider" + } } }, "collection_providers": { @@ -117,6 +169,38 @@ "8": "refinement_level_8", "9": "refinement_level_9", "10": "refinement_level_10"} + }, + "est_topo_twi_10m_elva_healpix_nested": { + "filepath": "testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr", + "id_col": "cell_ids", + "zone_groups":{ + "5": "refinement_level_5", + "6": "refinement_level_6", + "7": "refinement_level_7", + "8": "refinement_level_8", + "9": "refinement_level_9", + "10": "refinement_level_10", + "11": "refinement_level_11", + "12": "refinement_level_12", + "13": "refinement_level_13", + "14": "refinement_level_14" + } + }, + "est_topo_twi_10m_elva_healpix_zuniq": { + "filepath": "testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr", + "id_col": "cell_ids", + "zone_groups":{ + "5": "refinement_level_5", + "6": "refinement_level_6", + "7": "refinement_level_7", + "8": "refinement_level_8", + "9": "refinement_level_9", + "10": "refinement_level_10", + "11": "refinement_level_11", + "12": "refinement_level_12", + "13": "refinement_level_13", + "14": "refinement_level_14" + } } } } diff --git a/tests/test_cql/test_cql_data-retrieval_query_zone_data.py b/tests/test_cql/test_cql_data-retrieval_query_zone_data.py index 2919ca1..b356625 100644 --- a/tests/test_cql/test_cql_data-retrieval_query_zone_data.py +++ b/tests/test_cql/test_cql_data-retrieval_query_zone_data.py @@ -18,7 +18,7 @@ import numpy as np from dggal import Application, pydggal_setup, CRS, ogc, epsg, GeoExtent, Array, GeoPoint from dggal import IVEA7H, ISEA7H_Z7, rHEALPix, HEALPix - +import healpix_geo extra_conf = { "input_address_type": 'HIERNDX', @@ -71,6 +71,8 @@ 'ISEA7H_Z7': ISEA7H_Z7, 'HEALPIX': HEALPix} +healpixgeo_supported_grids_mapping = {"healpixgeo_nested": healpix_geo.nested, + "healpixgeo_zuniq": healpix_geo.zuniq} dggal_app = Application(appGlobals=globals()) pydggal_setup(dggal_app) working = tempfile.mkdtemp() @@ -82,6 +84,8 @@ support_grids[dggrs_id] = dggal_supported_grids_mapping[dggrs_config['parameters']['grid']]() elif ("igeo7_dggrs_provider" in dggrs_config["classname"]): support_grids[dggrs_id] = dggrid + elif ("healpixgeo_dggrs_provider" in dggrs_config["classname"]): + support_grids[dggrs_id] = healpixgeo_supported_grids_mapping[dggrs_id] for collection in collections: cid, collection_config = collection.popitem() @@ -117,7 +121,7 @@ dt = cp.datasources[datasource_id].filehandle zone_groups = cp.datasources[datasource_id].zone_groups ds1 = dt[zone_groups[str(rf1)]].to_dataset().to_dataframe().reset_index() - ds1 = ds1.drop('spatial_ref', axis=1) + ds1 = ds1.drop('spatial_ref', axis=1).reset_index() if ("saptial_ref" in ds1.columns) else ds1 elif (isinstance(cp, ParquetCollectionProvider)): parquetpath = cp.datasources[datasource_id].filepath ds1 = pd.read_parquet(parquetpath).reset_index() @@ -132,6 +136,12 @@ if (collection.collection_provider.dggrsId == 'igeo7'): ds1['textual_zone_id'] = ds1['zone_id'].apply(lambda x: z7hex_to_z7string(z7int_to_z7hex(x))) ds1 = ds1.drop('zone_id', axis=1) + elif ("healpixgeo" in collection.collection_provider.dggrsId): + if ("nested" in collection.collection_provider.dggrsId): + ds1['textual_zone_id'] = ds1['cell_ids'].apply(lambda x: f"{str(rf1).zfill(2)}_{str(x)}") + else: + ds1['textual_zone_id'] = ds1['cell_ids'].apply(lambda x: f"{str(x)}") + ds1 = ds1.drop('cell_ids', axis=1) else: mygrid = support_grids[collection.collection_provider.dggrsId] ds1['textual_zone_id'] = ds1['zone_id'].apply(lambda x: mygrid.getZoneTextID(x)) diff --git a/tests/test_cql/test_cql_zone-query_dggrs_zones.py b/tests/test_cql/test_cql_zone-query_dggrs_zones.py index 501b9cf..5e51cf7 100644 --- a/tests/test_cql/test_cql_zone-query_dggrs_zones.py +++ b/tests/test_cql/test_cql_zone-query_dggrs_zones.py @@ -107,10 +107,13 @@ # It is a data tree dt = cp.datasources[datasource_id].filehandle zone_groups = cp.datasources[datasource_id].zone_groups - ds1 = dt[zone_groups[str(rf1)]].to_dataset().to_dataframe() - ds2 = dt[zone_groups[str(rf2)]].to_dataset().to_dataframe() - ds1 = ds1.drop('spatial_ref', axis=1).reset_index() - ds2 = ds2.drop('spatial_ref', axis=1).reset_index() + ds1 = dt[zone_groups[str(rf1)]].to_dataset().to_dataframe().reset_index() + ds2 = dt[zone_groups[str(rf2)]].to_dataset().to_dataframe().reset_index() + if ("cell_ids" in ds1.columns or "cell_ids" in ds2.columns): + ds1 = ds1.rename(columns={"cell_ids": "zone_id"}) + ds2 = ds2.rename(columns={"cell_ids": "zone_id"}) + ds1 = ds1.drop('spatial_ref', axis=1).reset_index() if ("saptial_ref" in ds1.columns) else ds1 + ds2 = ds2.drop('spatial_ref', axis=1).reset_index() if ("saptial_ref" in ds2.columns) else ds2 test_rf = {rf1: [ds1], rf2: [ds2]} elif (isinstance(cp, ParquetCollectionProvider)): # duckdb connection @@ -172,6 +175,8 @@ def test_cql_zone_query_dggrs_zones(): test_value = np.nanmax(tdata) validation_data = dataset[dataset[column_names[0]] <= test_value] cql_string = cql_string.format(var_name=column_names[0], value=test_value) + if (validation_data.empty): + continue print(f"CQL to test: {cql_string}") print(f"test data: {validation_data}") print(f"Success test case with dggs zones query ({dggrsid}, bbox: {aoi.bounds}, zone_level={rf}, compact=False") diff --git a/tests/test_temporal_collections/test_temporal_zone-query_dggrs_zones.py b/tests/test_temporal_collections/test_temporal_zone-query_dggrs_zones.py index 110e3b6..c0ef83a 100644 --- a/tests/test_temporal_collections/test_temporal_zone-query_dggrs_zones.py +++ b/tests/test_temporal_collections/test_temporal_zone-query_dggrs_zones.py @@ -109,8 +109,8 @@ zone_groups = cp.datasources[datasource_id].zone_groups ds1 = dt[zone_groups[rf1]].to_dataset().to_dataframe() ds2 = dt[zone_groups[rf2]].to_dataset().to_dataframe() - ds1 = ds1.drop('spatial_ref', axis=1) - ds2 = ds2.drop('spatial_ref', axis=1) + ds1 = ds1.drop('spatial_ref', axis=1).reset_index() if ("saptial_ref" in ds1.columns) else ds1 + ds2 = ds2.drop('spatial_ref', axis=1).reset_index() if ("saptial_ref" in ds2.columns) else ds2 elif (isinstance(cp, ParquetCollectionProvider)): # duckdb connection parquetpath = cp.datasources[datasource_id].filepath diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..a979156ed34628b4edb4aa3bfd9cd0e87be75c01 GIT binary patch literal 119 zcmZQ#oWdx;$iM)?cT%h3pig7|y>6afK1(Rl2b)HMRnGv_If%(s_=HhK{7S(KezD#o3!*uRiDtkUW?2S7li#vu~d2+I*{P?nXWZ0p^$wQ(S@> bmPqrdcm>5t+_)ukYxkrT_rxSN?PCG}9o{W> literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/twi/zarr.json new file mode 100644 index 0000000..bcffe03 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 34 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_10/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..be53a5e0a67bb8b9e3eb321e3589ebfef148cc7b GIT binary patch literal 131 zcmZQ#oWdx;#J~W;%?u0-B0!uC#2Q=m|0W19@-j21FflR!AtN(06Od$JVrFJzV`B#a zHdYo^7Irp91_tE?0i}sn7n=6u9t^Ijm|5v{;sdwFOP6cs-d%E)EV`p>#HID+p-WR* asmC#Ix7!KcP8&A(|Cr#*bZN(G4@Lm3BO-nP literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/zarr.json new file mode 100644 index 0000000..d6101ae --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 66 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 11, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..9d7cd48f91b04a37763212ba8d546a3681eb43d2 GIT binary patch literal 251 zcmV0*8n7klkg96X z>}ud0cvMwl4`pNE9W)NSs*?f#e}RaB7>KokKu8h$=|;fd{O26Um@_zxG!A3_e1N$8 zr4+#cP#_ipq6qjeZk-itwJX(MhPE=ZDkSP(caiE|t4Nu3InzqyI&>MYU}k-mS?t!8 z@Je0iOv*B-fQZCS2Veq|+YHl#h*EHbzjP`nsI#6{i8p zG3c!W3tPw~EpXYkX~TBf=?@3IQs# BYa;*v literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/zarr.json new file mode 100644 index 0000000..54ebd54 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 66 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_11/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..b7ce2e95a7736998b8131dde059d95f0c3adfc10 GIT binary patch literal 213 zcmZQ#oWl5ng@FNtuQD(&hyd|kAlBHb|2N?W^ITR27iMNAAY^1JrF(K7!83}owmFoy8Ll@;Z9FUa(&DdS z{UeQ}c&`rMm0^?IA74@_kjpzW&06}~iI#WW3?bDe6I`TqD literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/zarr.json new file mode 100644 index 0000000..02eb5f4 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 159 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 12, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..021841144abcd8bec6dc837c0de9163acfdc7ec3 GIT binary patch literal 586 zcmV-Q0=4}D0h9sw1ONc|1ONa^0ssIM0000o0ssIgwJ-f(_ya`|0G4D`8K9-u~=^V3<;ZCUH6dMnqM%@khiva?z7HLSNkG%XS$U5zni0%ud7*_WxNpNMSE zOyB96i0HqouC2abQySgqikO*{bko|Y+8bHPf1T{cI+?mz*=p6R+ubEJtc}TTtReF= zQz~mGkVV=7kcbzcfyHH3fhZJ)8AdP|#+3nF`R>TN;wTQoz~c~#qQFA5!;HGTItnll z#E_YowVUZ9>hau@smnF2#7#gGC@vaPI3Z}qbTPf=NRQENODeQE`cSHz)=EA zK$0v+Im&Vjl9+%&l7J)w6tgIc31q18G)4$gN@xO-5*0i^0!Av08xfGDg{s1$6vovG z3i>MZD<(}8d?T3MNn(@0JLW%u1tGG43Pxv*#3^N;0}Rn-$dSV0eLbDqccpS;D-DCH zlmUmqEX~HW)q&RM!?YKn*}?cffq)_j_2iELpzJdbNDMrPcczrxplEaOrQmCzgSPN9 zsxX+P%qSHhU$^IMO3`PTwM^A#G636po0K`ibkI+Q*V7sOexO%f8x=VsnsrD)H2Mzo YA7de1axh;YcrQXAs!TLCq|8vN3t=S+wg3PC literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/zarr.json new file mode 100644 index 0000000..bc16b39 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 159 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_12/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..19fe9eb60efdc996b57525f36db28bfce88419f8 GIT binary patch literal 321 zcmV-H0lxkM0h9sg4gdh?4gdf_0RR9L0000f0RR9fwJ-f(=nee{05XLOjxsa=A`v1H z5LH!0MMXuu_ue@qr|rG>`^>#0NpijvGKMGw7%;oDGdl|)+y#JoKIpBgCGabbl zl(e%pO_KH}@PVtZmz;a$?ARrqe4R3^o z^-Y_TY2bN+RAi$v2_y<9Qm!(>NP#$Krd2zCu^-1-ZdO@zqe~xnPAbhs5Yijz%sv)X zOwOB3-enMT53M>AD|DHv66Y?I?1+K0{+l35^F}s(AO-J)-N&74WwtHml%0f9gXCu7 T1In^!DYaXP0+DnAe-=;%{+5Lx literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/zarr.json new file mode 100644 index 0000000..31dcde1 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 477 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 477 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 13, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_13/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..ddd1769afa64d6104a5ce5d3da1249a5ea5229d5 GIT binary patch literal 1753 zcmV;~1}6Ce0h9sg4gdh?4gdhz1^@sQ0002N1^@skwJ-f(=ncI!0P1oS5EsCw1KzWq z^{gK($N(&>1$efZlf(RlRHnx$%Hmdb9-%R59n#NRTHS!uPF&stYB)WbCX^z5`ey>eL< zO*73dHSv6`!$eD&n)QVBOZ(5XQcJv{ohj;#M#D19s{e)O%Tt#jmKE*vc4l;;>xWgQ zS?9VlrlggAExgZCrSv=1KjBr!sS2rMgbnY3p1449jYEtx{Wg?U{eRZgX0es-E#ywEpg&zKnKRXj*w!-nHZYy5_3d zD&KdzZnQG{{boICX`$J#X1lxgZ~d!^YPDthWoOgf^;SB(|7y>)<{4V1JUv=nX_&Uz z^n90U-5Kk8=0scm);!Zx->hDjcGI%G%2F{MH817m)t)aj)4H%q?9a%p(Ld+ z3S~)1q8P_+T6QTC1UZalmt6x%j3P-^S&BW3qCl1+9fpyLBqautjHE0IQtYl_HV|@& zLYL@-+fzg#z!Ok`0R@GMa7eSexE=z%18xX*iA%iz2O)#o71rzsL=nM|W;k$j%n@OO z4nQE>2mtV^z>H}G3+$>czyO1)4nZ0YIS@pEK@c3?5e`ux&|Y8`i9mxTf&m2;R5(OE zF$Ad?1Sk+mpg<@JClE)V5{I}N27(xfyA*B+hSMP^69GX4mF5gL191Zy@!cU<9EcoF z>Iu8T38?|@5t(@?Fz8GSX2cD<8)y-P-M*$S+gI9I;my!nHcXeEqG{a^oqBop)vvs+ zyscN6|Jwt*yL-%iTy|>Fk(oJV%BZRgpc`Zc4p0EvH3%>>X4jOF&tE_t004j=n^Q(M zWrmE6l~38gLFNZ;<_7`jnAxEFoGNpWSsxi0@cy7;Vd)pWTCy&p~@bfGIo2W~cxFFgM0k=JPEIWRK-Q5MxP< zRZ@`{M}Z{9Vi-w+EXtB>ffNd2B*l^JDoLUw%3%}>vU?y%u^wwc1X-5D zKokWj79&X(BPq#}5an2oBPo`{NJ)Vd3$i4IQYeR_AWMNksPQCJOqr1p5s@SXNjgyk zj8TqhN_`Q~C1fNrlBAH~_7GYN*9`p@jF&;jD8QnEIiQa7IpQ`a!&vB)Kc`2dMt4^( zG75_gWpq~_<}0QC@&N&<4ovl#F-z2`Cc)$!>=QlHj|7|1ft=`$1LV1>SE8`-O7?v{ zy5Z`*nNQP%_?V=4H;I$N36Q>8UM5|pkz};3ZY-JK)QP)in3?WKmRMdwWS<5Tb=5c( zB6^GZKNnH@H?G0-;OsL2z*o~9ji(MsL#Oj0TerC4L0aUuMu4?a1CKbtrtnvWoH}2O zc=USJH`cJTMzs0wUJzs*aHrLvHCwhAPot=~jeeCI^%J)x7?(@ZDk!?jqj0nh4c3f; z7|js#>cjlmR}z$)O1Ub=tJMQ%x;N7{>)9cs8Lt++O7C z*4x5*U6+P`ZDvXYnG2-rYbl$c3QBCv1AlRI^3Yj z)Q^o3fmi`t!cSaCSeg3#)GluEUtcZ6gTxo+eK5S&MvEm;wH*^ zWjPYe2$nY5Q4Sb65JYF#MD~9#og8Z&@!IG-Rv?F9-Dnxy@<#0@3UUb))fKQI9-t~Y v^F+sb!qg?-33Ps0L;va6*DV^5JCWROmK|3 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 T-EIVRM_y18kc|j9_{Ufa6hKRN literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/twi/zarr.json new file mode 100644 index 0000000..f6c0ab5 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 1589 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1589 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_14/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_5/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_5/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..f709179f12651b1b15c491b960c6f4365da85df2 GIT binary patch literal 88 ucmZQ#oW|(Ez`y{)5kP#M2}l6JT`1ka4BZzz`y{)89>az2PA-i6H0SIX>KUZ1EqPPG#`}ahtdjAS`kXCKxqKoJ_8N_ literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/cell_ids/zarr.json new file mode 100644 index 0000000..eb83669 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 11 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 6, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/twi/zarr.json new file mode 100644 index 0000000..cef0560 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 11 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_6/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..61bf775514abff5eb617bd889b38bc55c3966319 GIT binary patch literal 96 wcmZQ#oW>Zyz`y{)2|%oF0VIGx6H04CX&or73#Ij-v_6zJg3=aH+6qbo06J#{pa1{> literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/zarr.json new file mode 100644 index 0000000..be143c3 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 10 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 7, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/twi/zarr.json new file mode 100644 index 0000000..299182f --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 10 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_7/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..2e3d8cbf3dbec93d6b7a2cfe188f582f79778afa GIT binary patch literal 120 zcmZQ#oW_{Jz`y{)6+pc40gwQKO%K2n!)7SG1xjy)(%Yc)b|}3AO7Dcyd!Y1QDE$CR HKZ4Q#Pcs(V literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/zarr.json new file mode 100644 index 0000000..b474f3a --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 13 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 13 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 8, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..d5e1726b3fe318598261f8a8ec5c65a80c3ae0b0 GIT binary patch literal 120 jcmZQ#oW_{Jz`y{)6+jFDKmHR#U%MslK#V#D22Aq+&Knzk literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/zarr.json new file mode 100644 index 0000000..453ccb6 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 13 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 13 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_8/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..1484b36195c1963ac0a0e1de3b8c42263954012e GIT binary patch literal 82 zcmZQ#oWeMPfq?;pgMe5Bh^>HFW2^pOg$cck3=>#c*x1>?fRT}rfq{{cnQwu?+S`l` cOl@*!{x7gy`7y2k1K-k=uCHQKC1lvk0b$e>82|tP literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/zarr.json new file mode 100644 index 0000000..5d5fbb9 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 18 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 9, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..2e847f458acf424b7f8fc198b939d74b2dc9944b GIT binary patch literal 85 zcmZQ#oWeMPfq?;pLxETXh;4ybW2^pOg$YX;89p>PC^Rq>FdPt2I3Qqfz#-zmh8+ha eG!8%kr$WHtV~lq%@ZMd)|Fh|U)#lyG*-ilSSRV2K literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/zarr.json new file mode 100644 index 0000000..acdd3f7 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 18 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/refinement_level_9/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/zarr.json new file mode 100644 index 0000000..1039655 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_nested_datatree.zarr/zarr.json @@ -0,0 +1,1051 @@ +{ + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": { + "refinement_level_10": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_11": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_12": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_13": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_14": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_5": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_6": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_7": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_8": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_9": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_10/cell_ids": { + "shape": [ + 34 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 10, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_10/twi": { + "shape": [ + 34 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_11/cell_ids": { + "shape": [ + 66 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 11, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_11/twi": { + "shape": [ + 66 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_12/cell_ids": { + "shape": [ + 159 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 12, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_12/twi": { + "shape": [ + 159 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_13/cell_ids": { + "shape": [ + 477 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 477 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 13, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_13/twi": { + "shape": [ + 477 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 477 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_14/cell_ids": { + "shape": [ + 1589 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1589 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 14, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_14/twi": { + "shape": [ + 1589 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1589 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_5/cell_ids": { + "shape": [ + 9 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 5, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_5/twi": { + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_6/cell_ids": { + "shape": [ + 11 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 6, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_6/twi": { + "shape": [ + 11 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_7/cell_ids": { + "shape": [ + 10 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 7, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_7/twi": { + "shape": [ + 10 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_8/cell_ids": { + "shape": [ + 13 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 13 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 8, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_8/twi": { + "shape": [ + 13 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 13 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_9/cell_ids": { + "shape": [ + 18 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 9, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_9/twi": { + "shape": [ + 18 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + } + } + }, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..9f3db5dc19bafdea1d3b8680b04bb3739e0a4a25 GIT binary patch literal 113 zcmZQ#oWdx;$iM)?g$xV~B0wAo#2Q=m|0W19)G{&rcyQnV5FJ?1uwX$$0|*^Bpa4{% zpdetNV4$ENwLl>7%K@f6Rf=itiy9B+-t_gc?sycx?bbT^riR$sf^&1@1MOxOA3ZUx J{PHC!AppKhDy{$k literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/zarr.json new file mode 100644 index 0000000..0c5578f --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 34 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 10, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..06b06d72ae077931f57784fbd457ac5b318fa98c GIT binary patch literal 124 zcmZQ#oWdx;$iM)?H4F?4B0!u1#2Q=m|0W19TxDWNV&xM%pen0(Ip1g|U+w?=89%3= z;xS%%<}Ty^#>uRiDtkUW?2S7li#vu~d2+I*{P?nXWZ0p^$wQ(S@> bmPqrdcm>5t+_)ukYxkrT_rxSN?PCG}9o{W> literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/zarr.json new file mode 100644 index 0000000..bcffe03 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 34 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_10/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..ee2f50814b460b6ac10fc06a4b460a05096adac3 GIT binary patch literal 140 zcmZQ#oWdx;#J~W;Jq!#CB0yXM#2Q=m|0W19hB7luVLEW&zylCCu%KZ0*8n7klkg96X z>}ud0cvMwl4`pNE9W)NSs*?f#e}RaB7>KokKu8h$=|;fd{O26Um@_zxG!A3_e1N$8 zr4+#cP#_ipq6qjeZk-itwJX(MhPE=ZDkSP(caiE|t4Nu3InzqyI&>MYU}k-mS?t!8 z@Je0iOv*B-fQZCS2Veq|+YHl#h*EHbzjP`nsI#6{i8p zG3c!W3tPw~EpXYkX~TBf=?@3IQs# BYa;*v literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/twi/zarr.json new file mode 100644 index 0000000..54ebd54 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 66 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_11/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..05c9a9ed23c95927804fd55eb9b5075b8efb0bf3 GIT binary patch literal 210 zcmZQ#oWl5ng@FNtFEKDMhyd{}AlBHb|2N?Wb1N%@3iE*j2Oc~)0EQ19ENB424;MaM zFi-$O0R{mE1qA~G1qA^EASnRkgT%cTC`2way}+crnr|!PDZ?oXH{Wr){^0jC(Sw(2 ze&3j?@AgJ9lOx#oD_hBl8J``3b#iQ$%c|R5?>A_=;pMr}GFOA~;C-{^bBE8)3_9K> zC;2eKS*oMhVpH{zhsW5MU1x56XTlnlp{|xK+#t977mL&c*F)#)gq&k4TXyW>0RSM> BRZ9Q> literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/zarr.json new file mode 100644 index 0000000..ac39531 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 159 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 12, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..021841144abcd8bec6dc837c0de9163acfdc7ec3 GIT binary patch literal 586 zcmV-Q0=4}D0h9sw1ONc|1ONa^0ssIM0000o0ssIgwJ-f(_ya`|0G4D`8K9-u~=^V3<;ZCUH6dMnqM%@khiva?z7HLSNkG%XS$U5zni0%ud7*_WxNpNMSE zOyB96i0HqouC2abQySgqikO*{bko|Y+8bHPf1T{cI+?mz*=p6R+ubEJtc}TTtReF= zQz~mGkVV=7kcbzcfyHH3fhZJ)8AdP|#+3nF`R>TN;wTQoz~c~#qQFA5!;HGTItnll z#E_YowVUZ9>hau@smnF2#7#gGC@vaPI3Z}qbTPf=NRQENODeQE`cSHz)=EA zK$0v+Im&Vjl9+%&l7J)w6tgIc31q18G)4$gN@xO-5*0i^0!Av08xfGDg{s1$6vovG z3i>MZD<(}8d?T3MNn(@0JLW%u1tGG43Pxv*#3^N;0}Rn-$dSV0eLbDqccpS;D-DCH zlmUmqEX~HW)q&RM!?YKn*}?cffq)_j_2iELpzJdbNDMrPcczrxplEaOrQmCzgSPN9 zsxX+P%qSHhU$^IMO3`PTwM^A#G636po0K`ibkI+Q*V7sOexO%f8x=VsnsrD)H2Mzo YA7de1axh;YcrQXAs!TLCq|8vN3t=S+wg3PC literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/zarr.json new file mode 100644 index 0000000..bc16b39 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 159 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_12/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..d8fb8cca1f9c00ad6e4b43a11e30de372fc6e332 GIT binary patch literal 329 zcmV-P0k-}E0h9sg4gdh?4gdg20RR9L0000n0RR9fwJ-f(=nXvy0OCXq6Tl@MyEgyY z{A<+k)hr^LfOa+qNyfDj)(XkV-q#X{SNL3>6g>1%Q&EKv#x2 z;DQ=69iK-|^VEx^8X-?L&S1fIYeZL6~o4+*?000t$s4* z&I-EN0Xma)7v125LZPfv4WJl2`uRZjGMUagD~GZT6=?Hebk`st8zK#FgogD^o0Dna zd4g1AqcRC33MW#oGQvoKIA^9+JAbhs$60PxS#+aIA9zkG%|#H>8|lnG7FA5nn@rwi z5OWW$Iuk2&nW_@!E|lzufwTUbAWHK_Hhmxk?}XjQooi*bE#{P+gi?d#X5s_NvS=x_ bTZsaZbOL`uiNA_7C~c>0nk4N{;Dc4L9x{!X literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/zarr.json new file mode 100644 index 0000000..484b249 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 477 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 477 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 13, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/twi/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_13/twi/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..ddd1769afa64d6104a5ce5d3da1249a5ea5229d5 GIT binary patch literal 1753 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 T-EIVRM_y18kc|j9_{Ufa6hKRN literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/twi/zarr.json new file mode 100644 index 0000000..f6c0ab5 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 1589 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1589 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_14/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..db9b4b627188634fac4ef7f408e82f11803598d5 GIT binary patch literal 88 ucmZQ#oW|(Ez`y{)5kL$9>scZ6ZYa&j4&gIHX;vuB4yAdav>=ogW(NS?$pdKs literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/zarr.json new file mode 100644 index 0000000..4a594f3 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 9 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 5, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/twi/zarr.json new file mode 100644 index 0000000..f8ac013 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_5/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_6/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_6/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..d6a68f73a7c5a66e38dcdab8ce37b01c7a01a927 GIT binary patch literal 104 zcmZQ#oW>Zzz`y{)89)pM4lL|o`T&%6V1@Fbv;!NI52YR0p?oOqAOPhZyz`y{)2|x@6A6VGIv;ixGE`ZV#p!5bPeE~{;fYJeM5cLA=5ITUJ9RP-D B2Uh?9 literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/cell_ids/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/cell_ids/zarr.json new file mode 100644 index 0000000..278ef04 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/cell_ids/zarr.json @@ -0,0 +1,48 @@ +{ + "shape": [ + 10 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 7, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/twi/zarr.json new file mode 100644 index 0000000..299182f --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 10 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_7/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/cell_ids/c/0 b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_8/cell_ids/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..3e95547e5d9f8556888c5476efc5fa9be20fc0c6 GIT binary patch literal 120 zcmZQ#oW_{Jz`y{)6+jFIJ*@0tdJ2@D1ErTh=`~P#3zXgirH?@AGf?^olzs!HS=b@w IimU3>^;^G#r2c1qB5L1_cGa1qK)2CNQPC^Rq>FdPt2I3Qqfz#-zmh8+ha eG!8%kr$WHtV~lq%@ZMd)|Fh|U)#lyG*-ilSSRV2K literal 0 HcmV?d00001 diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/twi/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/twi/zarr.json new file mode 100644 index 0000000..acdd3f7 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/twi/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 18 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/zarr.json new file mode 100644 index 0000000..d1f97da --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/refinement_level_9/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/zarr.json b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/zarr.json new file mode 100644 index 0000000..c5bbe23 --- /dev/null +++ b/tests/testing_data/est_topo_twi_clipped_10m_healpix_zuniq_datatree.zarr/zarr.json @@ -0,0 +1,1051 @@ +{ + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": { + "refinement_level_10": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_11": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_12": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_13": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_14": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_5": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_6": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_7": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_8": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_9": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "refinement_level_10/cell_ids": { + "shape": [ + 34 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 10, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_10/twi": { + "shape": [ + 34 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 34 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_11/cell_ids": { + "shape": [ + 66 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 11, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_11/twi": { + "shape": [ + 66 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 66 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_12/cell_ids": { + "shape": [ + 159 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 12, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_12/twi": { + "shape": [ + 159 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 159 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_13/cell_ids": { + "shape": [ + 477 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 477 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 13, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_13/twi": { + "shape": [ + 477 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 477 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_14/cell_ids": { + "shape": [ + 1589 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1589 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 14, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_14/twi": { + "shape": [ + 1589 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1589 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_5/cell_ids": { + "shape": [ + 9 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 5, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_5/twi": { + "shape": [ + 9 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 9 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_6/cell_ids": { + "shape": [ + 11 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 6, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_6/twi": { + "shape": [ + 11 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_7/cell_ids": { + "shape": [ + 10 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 7, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_7/twi": { + "shape": [ + 10 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_8/cell_ids": { + "shape": [ + 13 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 13 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 8, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_8/twi": { + "shape": [ + 13 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 13 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_9/cell_ids": { + "shape": [ + 18 + ], + "data_type": "uint64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "grid_name": "healpix", + "level": 9, + "indexing_scheme": "nested" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "refinement_level_9/twi": { + "shape": [ + 18 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 18 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "NaN", + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "numcodecs.blosc", + "configuration": { + "cname": "zstd", + "clevel": 3, + "shuffle": 2 + } + } + ], + "attributes": { + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "cell_ids" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + } + } + }, + "node_type": "group" +} \ No newline at end of file From ad1516f230b7f56d8744986c9c7684a31e26e4f2 Mon Sep 17 00:00:00 2001 From: Tik Date: Thu, 24 Sep 2026 11:11:13 +0300 Subject: [PATCH 08/10] - added test cases for converting zuniq to nested index --- .../healpixgeo_dggrs_provider.py | 29 ++- tests/dggs_api_config_testing.json | 3 +- ...healpix_provider_core_dggrs_description.py | 31 +++ ...st_healpix_provider_core_dggrs_zoneinfo.py | 144 +++++++++++ ...provider_data-retrieval_query_zone_data.py | 237 ++++++++++++++++++ ...trieval_query_zone_data_zuniq_to_nested.py | 227 +++++++++++++++++ ...healpix_provider_zone-query_dggrs_zones.py | 209 +++++++++++++++ 7 files changed, 864 insertions(+), 16 deletions(-) create mode 100644 tests/test_healpixgeo/test_healpix_provider_core_dggrs_description.py create mode 100644 tests/test_healpixgeo/test_healpix_provider_core_dggrs_zoneinfo.py create mode 100644 tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data.py create mode 100644 tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data_zuniq_to_nested.py create mode 100644 tests/test_healpixgeo/test_healpix_provider_zone-query_dggrs_zones.py diff --git a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py index 1fdf6e2..2e87e69 100644 --- a/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py +++ b/pydggsapi/dependencies/dggrs_providers/healpixgeo_dggrs_provider.py @@ -208,25 +208,24 @@ def __init__(self, **params): self.ellipsoid = params.get("ellipsoid", "wgs84").lower() if (self.ellipsoid not in supported_ellipsoid): raise ValueError(f"{__name__} {self.ellipsoid} not supported") + self.support_conversion = params.get("support_conversion", {}) self.ellipsoid = self.ellipsoid.upper() - nested_conversion_properties = conversion_properties(zonelevel_offset=0) - self.dggrs_conversion = {"nested": nested_conversion_properties} + self.dggrs_conversion = {dggrsid: conversion_properties(**conversion_param) + for dggrsid, conversion_param in self.support_conversion.items()} def convert(self, zoneIds: List[str], targetdggrs: str, zone_id_repr: ZoneIdRepresentationType = 'textual') -> DGGRSProviderConversionReturn: - match targetdggrs: - - case "nested": - # in the case of healpix, conversion from zuniq index to nested is a 1-to-1 mapping. - nestedIds, refinement_level = healpix_geo.zuniq.to_nested(zoneIds) - nestedIds, refinement_level = nestedIds.tolist(), refinement_level.tolist() - if (zone_id_repr == 'textual'): - nestedIds = list(map(lambda x: f"{refinement_level[x]}_{nestedIds[x]}", - range(len(nestedIds)))) - return DGGRSProviderConversionReturn(zoneIds=zoneIds, target_zoneIds=nestedIds, - target_res=refinement_level) - case _: - raise Exception(f"{__name__} conversion to {targetdggrs} not supported.") + if (targetdggrs not in self.dggrs_conversion.keys()): + raise Exception(f"{__name__} conversion to {targetdggrs} not supported.") + else: + # in the case of healpix, conversion from zuniq index to nested is a 1-to-1 mapping. + nestedIds, refinement_level = healpix_geo.zuniq.to_nested(zoneIds) + nestedIds, refinement_level = nestedIds.tolist(), refinement_level.tolist() + if (zone_id_repr == 'textual'): + nestedIds = list(map(lambda x: f"{refinement_level[x]}_{nestedIds[x]}", + range(len(nestedIds)))) + return DGGRSProviderConversionReturn(zoneIds=zoneIds, target_zoneIds=nestedIds, + target_res=refinement_level) # at the time of implementation, healpix_geo doesn't support textural repr of zone id # so assume the input is just uint64 in str diff --git a/tests/dggs_api_config_testing.json b/tests/dggs_api_config_testing.json index 10eb4de..a721d9f 100644 --- a/tests/dggs_api_config_testing.json +++ b/tests/dggs_api_config_testing.json @@ -146,7 +146,8 @@ "shapeType": "rhombus", "definition_link": "https://healpix-geo.readthedocs.io/en/latest/healpix/index.html", "defaultDepth": 1, - "classname": "healpixgeo_dggrs_provider.HealpixGeoZuniqProvider" + "classname": "healpixgeo_dggrs_provider.HealpixGeoZuniqProvider", + "parameters": {"support_conversion": {"healpixgeo_nested": {"zonelevel_offset": 0}}} } } }, diff --git a/tests/test_healpixgeo/test_healpix_provider_core_dggrs_description.py b/tests/test_healpixgeo/test_healpix_provider_core_dggrs_description.py new file mode 100644 index 0000000..2a72feb --- /dev/null +++ b/tests/test_healpixgeo/test_healpix_provider_core_dggrs_description.py @@ -0,0 +1,31 @@ +from pydggsapi.schemas.ogc_dggs.dggrs_descrption import DggrsDescription +from fastapi.testclient import TestClient +from tinydb import TinyDB +import pytest +from importlib import reload +import os + +support_grids = [] + +db = TinyDB(os.environ.get('DGGS_API_CONFIG')) +all_dggrs = db.table('dggrs').all() + +for dggrs in all_dggrs: + dggrs_id, dggrs_config = dggrs.popitem() + if ("healpixgeo_dggrs_provider" in dggrs_config["classname"]): + support_grids.append(dggrs_id) + +assert len(support_grids) > 0 + + +def test_core_dggrs_description(): + import pydggsapi.api + app = reload(pydggsapi.api).app + client = TestClient(app) + + for grid in support_grids: + print(f"Success test case with dggs description {grid}") + response = client.get(f'/dggs-api/dggs/{grid}') + assert response.status_code == 200 + assert DggrsDescription(**response.json()) + diff --git a/tests/test_healpixgeo/test_healpix_provider_core_dggrs_zoneinfo.py b/tests/test_healpixgeo/test_healpix_provider_core_dggrs_zoneinfo.py new file mode 100644 index 0000000..6cf6bdc --- /dev/null +++ b/tests/test_healpixgeo/test_healpix_provider_core_dggrs_zoneinfo.py @@ -0,0 +1,144 @@ +from pydggsapi.schemas.ogc_dggs.dggrs_zones_info import ZoneInfoResponse +from pydggsapi.schemas.api.collections import Collection +from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint +from pydggsapi.dependencies.dggrs_providers.dggal_dggrs_provider import generateZoneGeometry + +from fastapi.testclient import TestClient +import pytest +import os +from importlib import reload +from tinydb import TinyDB +import geopandas as gpd +import numpy as np +import shapely +import healpix_geo +from typing import List + +supported_grids_mapping = {"healpixgeo_nested": healpix_geo.nested, + "healpixgeo_zuniq": healpix_geo.zuniq} + +support_grids = {} + +db = TinyDB(os.environ.get('DGGS_API_CONFIG')) +all_dggrs = db.table('dggrs').all() +collections = db.table('collections').all() +collections_dict = {} + +def _nested_generateZoneGeometries(zoneIds: List[int], refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.nested.healpix_to_lonlat(zoneIds, refinement_level, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.nested.vertices(zoneIds, refinement_level, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + +def _zuniq_generateZoneGeometries(zoneIds: List[int], refinement_level: None, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.zuniq.healpix_to_lonlat(zoneIds, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.zuniq.vertices(zoneIds, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + +for dggrs in all_dggrs: + dggrs_id, dggrs_config = dggrs.popitem() + if ("healpixgeo_dggrs_provider" in dggrs_config["classname"]): + support_grids[dggrs_id] = supported_grids_mapping[dggrs_id] + + +assert len(support_grids.keys()) > 0 + + +for collection in collections: + cid, collection_config = collection.popitem() + if (collection_config['collection_provider']['dggrsId'] in support_grids): + collection_config['id'] = cid + collections_dict[cid] = Collection(**collection_config) + +assert len(collections_dict.keys()) > 0 + +validation_df = {} +for collection_name, collection in collections_dict.items(): + bbox = collection.extent.spatial.bbox[0] + # create a smaller bbox + #minx, miny = round(minx + 0.1, 3), round(miny + 0.05, 3) + #maxx, maxy = round(maxx - 0.1, 3), round(maxy - 0.05, 3) + rf = collection.collection_provider.max_refinement_level - 1 + mygrid = support_grids[collection.collection_provider.dggrsId] + results = mygrid.zone_coverage(tuple(bbox), rf, ellipsoid="WGS84", flat=False) + zones_ids = results[0] + + if ("nested" in collection.collection_provider.dggrsId): + generateZoneGeometry = _nested_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(rf).zfill(2)}_{str(x)}", zones_ids)) + else: + generateZoneGeometry = _zuniq_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(x)}", zones_ids)) + geometry = generateZoneGeometry(zones_ids, rf, False) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + hex_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + geometry = generateZoneGeometry(zones_ids, rf, True) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + centroid_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + validation_df[collection_name] = {'dggrsid': collection.collection_provider.dggrsId, + 'hex': hex_df, 'centroid': centroid_df.set_index('zone_id')} + + +def test_healpix_core_dggs_zoneinfo(): + import pydggsapi.api + app = reload(pydggsapi.api).app + client = TestClient(app) + + for collection_name, df_dict in validation_df.items(): + iloc_pos = np.random.randint(0, df_dict['hex'].shape[0], 1) + dggrsid = df_dict['dggrsid'] + zone = df_dict['hex'].iloc[iloc_pos[0]] + zone_centroid_geometry = df_dict['centroid'].loc[zone['zone_id']]['geometry'] + print(f"Success test case with dggs zone info ({dggrsid} {zone['zone_id']})") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}') + zoneinfo = ZoneInfoResponse(**response.json()) + centroid = shapely.geometry.shape(zoneinfo.centroid.__dict__) + hexagon = shapely.geometry.shape(zoneinfo.geometry.__dict__) + assert hexagon.equals_exact(zone['geometry']) + assert centroid.equals_exact(zone_centroid_geometry) + assert response.status_code == 200 + + print("Fail test case with collections (non-existing dggrs id)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/not_exit/zones/13_00000000') + assert "not supported" in response.text + assert response.status_code == 400 + + print(f'Success test case with collections on zones info ({collection_name}, {dggrsid}, {zone["zone_id"]})') + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}') + zoneinfo = ZoneInfoResponse(**response.json()) + centroid = shapely.geometry.shape(zoneinfo.centroid.__dict__) + hexagon = shapely.geometry.shape(zoneinfo.geometry.__dict__) + assert hexagon.equals_exact(zone['geometry']) + assert centroid.equals_exact(zone_centroid_geometry) + assert response.status_code == 200 + + print(f"Fail test case with collections on non-exist zones info ({collection_name}, {dggrsid}, 13_00000000)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/13_00000000') + assert response.status_code == 204 diff --git a/tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data.py b/tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data.py new file mode 100644 index 0000000..77aed7d --- /dev/null +++ b/tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data.py @@ -0,0 +1,237 @@ +from pydggsapi.schemas.ogc_dggs.dggrs_zones_data import ZonesDataDggsJsonResponse, ZonesDataGeoJson +from pydggsapi.schemas.api.collections import Collection +from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint +from pydggsapi.dependencies.dggrs_providers.dggal_dggrs_provider import generateZoneGeometry +from fastapi.testclient import TestClient +import pytest +from importlib import reload +import os +import shapely +import json +from tinydb import TinyDB +import geopandas as gpd +import numpy as np +import zarr +import healpix_geo +from typing import List + +supported_grids_mapping = {"healpixgeo_nested": healpix_geo.nested, + "healpixgeo_zuniq": healpix_geo.zuniq} + +support_grids = {} + +db = TinyDB(os.environ.get('DGGS_API_CONFIG')) +all_dggrs = db.table('dggrs').all() +collections = db.table('collections').all() +collections_dict = {} + + +def _nested_generateZoneGeometries(zoneIds: List[int], refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.nested.healpix_to_lonlat(zoneIds, refinement_level, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.nested.vertices(zoneIds, refinement_level, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + + +def _zuniq_generateZoneGeometries(zoneIds: List[int], refinement_level: None, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.zuniq.healpix_to_lonlat(zoneIds, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.zuniq.vertices(zoneIds, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + + +non_exist_aoi = shapely.Polygon([[113.81837742963569, 22.521237932154797], + [113.81837742963569, 22.13760392858767], + [114.41438573041694, 22.13760392858767], + [114.41438573041694, 22.521237932154797]]) + + +for dggrs in all_dggrs: + dggrs_id, dggrs_config = dggrs.popitem() + if ("healpixgeo_dggrs_provider" in dggrs_config["classname"]): + support_grids[dggrs_id] = supported_grids_mapping[dggrs_id] + +assert len(support_grids.keys()) > 0 + + +for collection in collections: + cid, collection_config = collection.popitem() + if (collection_config['collection_provider']['dggrsId'] in list(support_grids.keys())): + collection_config['id'] = cid + collections_dict[cid] = Collection(**collection_config) + +assert len(collections_dict.keys()) > 0 + +validation_df = {} +for collection_name, collection in collections_dict.items(): + minx, miny, maxx, maxy = collection.extent.spatial.bbox[0] + # create a smaller bbox + minx, miny = round(minx + 0.1, 3), round(miny + 0.05, 3) + maxx, maxy = round(maxx - 0.1, 3), round(maxy - 0.05, 3) + rf = collection.collection_provider.max_refinement_level - 2 + + mygrid = support_grids[collection.collection_provider.dggrsId] + results = mygrid.zone_coverage((minx, miny, maxx, maxy), rf, ellipsoid="WGS84", flat=False) + zones_ids = results[0] + + if ("nested" in collection.collection_provider.dggrsId): + generateZoneGeometry = _nested_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(rf).zfill(2)}_{str(x)}", zones_ids)) + else: + generateZoneGeometry = _zuniq_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(x)}", zones_ids)) + + geometry = generateZoneGeometry(zones_ids, rf, False) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + hex_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + geometry = generateZoneGeometry(zones_ids, rf, True) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + centroid_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + + non_exist_bbox = non_exist_aoi.bounds + non_exist_zone_ids = mygrid.zone_coverage(tuple(non_exist_bbox), rf, ellipsoid="WGS84", flat=False) + non_exist_zone_ids = non_exist_zone_ids[0] + if ("nested" in collection.collection_provider.dggrsId): + textual_non_exist_zone_ids = list(map(lambda x: f"{str(rf).zfill(2)}_{str(x)}", non_exist_zone_ids)) + else: + textual_non_exist_zone_ids = list(map(lambda x: f"{str(x)}", non_exist_zone_ids)) + + validation_df[collection_name] = {'hex': hex_df, 'centroid': centroid_df.set_index('zone_id'), + 'dggrsid': collection.collection_provider.dggrsId, + 'max_rf': collection.collection_provider.max_refinement_level, + 'zone_rf': rf, + 'non_exist_zoneids': textual_non_exist_zone_ids + } + + +def test_healpixgeo_data_retrieval(): + import pydggsapi.api + app = reload(pydggsapi.api).app + client = TestClient(app) + + print("Fail test case with non existing dggrs id") + response = client.get('/dggs-api/dggs/non_exist/zones/0013415612/data') + assert "not supported" in response.text + assert response.status_code == 400 + + for collection_name, df_dict in validation_df.items(): + iloc_pos = np.random.randint(0, df_dict['hex'].shape[0], 1) + max_rf = df_dict['max_rf'] + zone_rf = df_dict['zone_rf'] + dggrsid = df_dict['dggrsid'] + zone = df_dict['hex'].iloc[iloc_pos[0]] + over_rf_depth = max_rf - zone_rf + 1 + print(f"Fail test case withdata-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth={over_rf_depth}) over refinement") + response = client.get(f"/dggs-api/dggs/{dggrsid}/zones/{zone['zone_id']}/data", params={"zone-depth": over_rf_depth}) + assert "over refinement" in response.text + assert response.status_code == 400 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, default zone-depth = 1)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data') + assert response.status_code == 200 + data = ZonesDataDggsJsonResponse(**response.json()) + for k, v in data.values.items(): + assert len(v) == 1 + assert v[0].depth == 1 + assert len(v[0].data) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0 ,return = geojson)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', headers={'accept': 'application/geo+json'}, + params={'zone-depth': '0'}) + assert response.status_code == 200 + data = ZonesDataGeoJson(**response.json()) + assert len(data.features) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0,return = geojson, geometry='zone-centroid')") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', params={'geometry': 'zone-centroid', 'zone-depth': '0'}, + headers={'accept': 'application/geo+json'}) + assert response.status_code == 200 + data = ZonesDataGeoJson(**response.json()) + assert len(data.features) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=2)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', params={'zone-depth': '2'}) + data = ZonesDataDggsJsonResponse(**response.json()) + assert response.status_code == 200 + for k, v in data.values.items(): + assert len(v) == 1 + assert v[0].depth == 2 + assert len(v[0].data) > 0 + assert response.status_code == 200 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=1-2)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', params={'zone-depth': '1-2'}) + data = ZonesDataDggsJsonResponse(**response.json()) + assert response.status_code == 200 + zone_depth_counts = [1, 2] + for k, v in data.values.items(): + assert len(v) == 2 + for data in v: + assert data.depth in zone_depth_counts + assert len(data.data) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0-2)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', params={'zone-depth': '0-2'}) + data = ZonesDataDggsJsonResponse(**response.json()) + assert response.status_code == 200 + zone_depth_counts = [0, 1, 2] + for k, v in data.values.items(): + assert len(v) == 3 + for data in v: + assert data.depth in zone_depth_counts + assert len(data.data) > 0 + assert response.status_code == 200 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0-2, geometry='zone-centroid', return=geojson)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', params={'zone-depth': '0-2', 'geometry': 'zone-centroid'}, + headers={'accept': 'application/geo+json'}) + data = ZonesDataGeoJson(**response.json()) + assert len(data.features) > 0 + assert response.status_code == 200 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0-2, geometry='zone-centroid', return=zarr+zip)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + params={'zone-depth': '0-2', 'geometry': 'zone-centroid'}, + headers={'accept': 'application/zarr+zip'}) + assert response.status_code == 200 + with open("data_zarr.zip", "wb") as f: + f.write(response.content) + store = zarr.storage.ZipStore("data_zarr.zip", read_only=True) + z = zarr.open(store=store, mode="r") + print(z.tree()) + + iloc_pos = np.random.randint(0, len(df_dict['non_exist_zoneids']), 1) + non_exists_zoneid = df_dict['non_exist_zoneids'][iloc_pos[0]] + print(f"Empty test case with data-retrieval query ({dggrsid}, {non_exists_zoneid}, zone-depth=0-2, geometry='zone-centroid', return=geojson)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{non_exists_zoneid}/data', + params={'zone-depth': '0-2', 'geometry': 'zone-centroid'}, + headers={'accept': 'application/geo+json'}) + assert response.status_code == 204 + + diff --git a/tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data_zuniq_to_nested.py b/tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data_zuniq_to_nested.py new file mode 100644 index 0000000..96fd94c --- /dev/null +++ b/tests/test_healpixgeo/test_healpix_provider_data-retrieval_query_zone_data_zuniq_to_nested.py @@ -0,0 +1,227 @@ +from pydggsapi.schemas.ogc_dggs.dggrs_zones_data import ZonesDataDggsJsonResponse, ZonesDataGeoJson +from pydggsapi.schemas.api.collections import Collection +from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint +from pydggsapi.dependencies.dggrs_providers.dggal_dggrs_provider import generateZoneGeometry +from fastapi.testclient import TestClient +import pytest +from importlib import reload +import os +import shapely +import json +from tinydb import TinyDB +import geopandas as gpd +import numpy as np +import zarr +import healpix_geo +from typing import List + +support_grids = {"healpixgeo_zuniq": healpix_geo.zuniq} + + +db = TinyDB(os.environ.get('DGGS_API_CONFIG')) +all_dggrs = db.table('dggrs').all() +collections = db.table('collections').all() +collections_dict = {} + + +def _nested_generateZoneGeometries(zoneIds: List[int], refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.nested.healpix_to_lonlat(zoneIds, refinement_level, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.nested.vertices(zoneIds, refinement_level, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + + +def _zuniq_generateZoneGeometries(zoneIds: List[int], refinement_level: None, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.zuniq.healpix_to_lonlat(zoneIds, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.zuniq.vertices(zoneIds, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + + +non_exist_aoi = shapely.Polygon([[113.81837742963569, 22.521237932154797], + [113.81837742963569, 22.13760392858767], + [114.41438573041694, 22.13760392858767], + [114.41438573041694, 22.521237932154797]]) + +# only look for nested indexed collections + +# test on nested indexed collections only +for collection in collections: + cid, collection_config = collection.popitem() + if (collection_config['collection_provider']['dggrsId'] in ["healpixgeo_nested"]): + collection_config['id'] = cid + collections_dict[cid] = Collection(**collection_config) + +assert len(collections_dict.keys()) > 0 + +validation_df = {} +for collection_name, collection in collections_dict.items(): + minx, miny, maxx, maxy = collection.extent.spatial.bbox[0] + # create a smaller bbox + minx, miny = round(minx + 0.1, 3), round(miny + 0.05, 3) + maxx, maxy = round(maxx - 0.1, 3), round(maxy - 0.05, 3) + rf = collection.collection_provider.max_refinement_level - 2 + mygrid = support_grids["healpixgeo_zuniq"] + results = mygrid.zone_coverage((minx, miny, maxx, maxy), rf, ellipsoid="WGS84", flat=False) + zuniq_zones_ids = results[0] + + generateZoneGeometry = _zuniq_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(x)}", zuniq_zones_ids)) + + geometry = generateZoneGeometry(zuniq_zones_ids, rf, False) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + hex_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + geometry = generateZoneGeometry(zuniq_zones_ids, rf, True) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + centroid_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + + non_exist_bbox = non_exist_aoi.bounds + non_exist_zone_ids = mygrid.zone_coverage(tuple(non_exist_bbox), rf, ellipsoid="WGS84", flat=False) + non_exist_zone_ids = non_exist_zone_ids[0] + textual_non_exist_zone_ids = list(map(lambda x: f"{str(x)}", non_exist_zone_ids)) + + validation_df[collection_name] = {'hex': hex_df, 'centroid': centroid_df.set_index('zone_id'), + 'dggrsid': "healpixgeo_zuniq", + 'max_rf': collection.collection_provider.max_refinement_level, + 'zone_rf': rf, + 'non_exist_zoneids': textual_non_exist_zone_ids + } + + +def test_healpixgeo_data_retrieval_zuniq_to_nested(): + import pydggsapi.api + app = reload(pydggsapi.api).app + client = TestClient(app) + + print("Fail test case with non existing dggrs id") + response = client.get('/dggs-api/dggs/non_exist/zones/0013415612/data') + assert "not supported" in response.text + assert response.status_code == 400 + + for collection_name, df_dict in validation_df.items(): + iloc_pos = np.random.randint(0, df_dict['hex'].shape[0], 1) + max_rf = df_dict['max_rf'] + zone_rf = df_dict['zone_rf'] + dggrsid = df_dict['dggrsid'] + zone = df_dict['hex'].iloc[iloc_pos[0]] + over_rf_depth = max_rf - zone_rf + 1 + print(f"Fail test case withdata-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth={over_rf_depth}) over refinement") + response = client.get(f"/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone['zone_id']}/data", + params={"zone-depth": over_rf_depth}) + assert "over refinement" in response.text + assert response.status_code == 400 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, default zone-depth = 1)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data') + assert response.status_code == 200 + data = ZonesDataDggsJsonResponse(**response.json()) + for k, v in data.values.items(): + assert len(v) == 1 + assert v[0].depth == 1 + assert len(v[0].data) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0 ,return = geojson)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + headers={'accept': 'application/geo+json'}, + params={'zone-depth': '0'}) + assert response.status_code == 200 + data = ZonesDataGeoJson(**response.json()) + assert len(data.features) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0,return = geojson, geometry='zone-centroid')") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + params={'geometry': 'zone-centroid', 'zone-depth': '0'}, + headers={'accept': 'application/geo+json'}) + assert response.status_code == 200 + data = ZonesDataGeoJson(**response.json()) + assert len(data.features) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=2)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + params={'zone-depth': '2'}) + data = ZonesDataDggsJsonResponse(**response.json()) + assert response.status_code == 200 + for k, v in data.values.items(): + assert len(v) == 1 + assert v[0].depth == 2 + assert len(v[0].data) > 0 + assert response.status_code == 200 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=1-2)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + params={'zone-depth': '1-2'}) + data = ZonesDataDggsJsonResponse(**response.json()) + assert response.status_code == 200 + zone_depth_counts = [1, 2] + for k, v in data.values.items(): + assert len(v) == 2 + for data in v: + assert data.depth in zone_depth_counts + assert len(data.data) > 0 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0-2)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + params={'zone-depth': '0-2'}) + data = ZonesDataDggsJsonResponse(**response.json()) + assert response.status_code == 200 + zone_depth_counts = [0, 1, 2] + for k, v in data.values.items(): + assert len(v) == 3 + for data in v: + assert data.depth in zone_depth_counts + assert len(data.data) > 0 + assert response.status_code == 200 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0-2, geometry='zone-centroid', return=geojson)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', params={'zone-depth': '0-2', 'geometry': 'zone-centroid'}, + headers={'accept': 'application/geo+json'}) + data = ZonesDataGeoJson(**response.json()) + assert len(data.features) > 0 + assert response.status_code == 200 + + print(f"Success test case with data-retrieval query ({dggrsid}, {zone['zone_id']}, zone-depth=0-2, geometry='zone-centroid', return=zarr+zip)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{zone["zone_id"]}/data', + params={'zone-depth': '0-2', 'geometry': 'zone-centroid'}, + headers={'accept': 'application/zarr+zip'}) + assert response.status_code == 200 + with open("data_zarr.zip", "wb") as f: + f.write(response.content) + store = zarr.storage.ZipStore("data_zarr.zip", read_only=True) + z = zarr.open(store=store, mode="r") + print(z.tree()) + + iloc_pos = np.random.randint(0, len(df_dict['non_exist_zoneids']), 1) + non_exists_zoneid = df_dict['non_exist_zoneids'][iloc_pos[0]] + print(f"Empty test case with data-retrieval query ({dggrsid}, {non_exists_zoneid}, zone-depth=0-2, geometry='zone-centroid', return=geojson)") + response = client.get(f'/dggs-api/collections/{collection_name}/dggs/{dggrsid}/zones/{non_exists_zoneid}/data', + params={'zone-depth': '0-2', 'geometry': 'zone-centroid'}, + headers={'accept': 'application/geo+json'}) + assert response.status_code == 204 + + diff --git a/tests/test_healpixgeo/test_healpix_provider_zone-query_dggrs_zones.py b/tests/test_healpixgeo/test_healpix_provider_zone-query_dggrs_zones.py new file mode 100644 index 0000000..0df37bc --- /dev/null +++ b/tests/test_healpixgeo/test_healpix_provider_zone-query_dggrs_zones.py @@ -0,0 +1,209 @@ +from pydggsapi.schemas.ogc_dggs.dggrs_zones import ZonesResponse, ZonesGeoJson +from pydggsapi.schemas.api.collections import Collection +from pydggsapi.schemas.common_geojson import GeoJSONPolygon, GeoJSONPoint +from pydggsapi.dependencies.dggrs_providers.dggal_dggrs_provider import generateZoneGeometry +from fastapi.testclient import TestClient +import pytest +from importlib import reload +import os +from tinydb import TinyDB +import numpy as np +import shapely +import json +import geopandas as gpd +import healpix_geo +from typing import List + +supported_grids_mapping = {"healpixgeo_nested": healpix_geo.nested, + "healpixgeo_zuniq": healpix_geo.zuniq} + +support_grids = {} + +db = TinyDB(os.environ.get('DGGS_API_CONFIG')) +all_dggrs = db.table('dggrs').all() +collections = db.table('collections').all() +collections_dict = {} + + +def _nested_generateZoneGeometries(zoneIds: List[int], refinement_level: int, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.nested.healpix_to_lonlat(zoneIds, refinement_level, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.nested.vertices(zoneIds, refinement_level, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + + +def _zuniq_generateZoneGeometries(zoneIds: List[int], refinement_level: None, centroids: bool = False) -> GeoJSONPoint | GeoJSONPolygon | None: + if centroids: + lon, lat = healpix_geo.zuniq.healpix_to_lonlat(zoneIds, "WGS84") + points = np.stack([lon, lat], axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 2] + if (len(zoneIds) == 1): + points = np.squeeze(points, axis=1) + points = list(map(lambda x: GeoJSONPoint(type="Point", coordinates=(x[0], x[1])), points)) + return points + else: + lon, lat = healpix_geo.zuniq.vertices(zoneIds, "WGS84") + vertices = np.stack([lon, lat], axis=-1) + vertices = np.squeeze(vertices.view(dtype=np.dtype([('x', 'float64'), ('y', 'float64')])), axis=-1) + # for list only consist of one zone, the shape of vertices is [1, 1, 4] + if (len(zoneIds) == 1): + vertices = np.squeeze(vertices, axis=1) + polygon = list(map(lambda x: GeoJSONPolygon(type="Polygon", coordinates=[x.tolist()]), vertices)) + return polygon + + +non_exist_aoi = shapely.Polygon([[113.81837742963569, 22.521237932154797], + [113.81837742963569, 22.13760392858767], + [114.41438573041694, 22.13760392858767], + [114.41438573041694, 22.521237932154797]]) + + +for dggrs in all_dggrs: + dggrs_id, dggrs_config = dggrs.popitem() + if ("healpixgeo_dggrs_provider" in dggrs_config["classname"]): + support_grids[dggrs_id] = supported_grids_mapping[dggrs_id] + +assert len(support_grids.keys()) > 0 + + +for collection in collections: + cid, collection_config = collection.popitem() + if (collection_config['collection_provider']['dggrsId'] in list(support_grids.keys())): + collection_config['id'] = cid + collections_dict[cid] = Collection(**collection_config) + +assert len(collections_dict.keys()) > 0 + +validation_df = {} +for collection_name, collection in collections_dict.items(): + bbox = collection.extent.spatial.bbox[0] + # create a smaller bbox + # minx, miny = round(minx + 0.1, 3), round(miny + 0.05, 3) + # maxx, maxy = round(maxx - 0.1, 3), round(maxy - 0.05, 3) + rf = collection.collection_provider.max_refinement_level - 2 + mygrid = support_grids[collection.collection_provider.dggrsId] + results = mygrid.zone_coverage(tuple(bbox), rf, ellipsoid="WGS84", flat=False) + zones_ids = results[0] + + if ("nested" in collection.collection_provider.dggrsId): + generateZoneGeometry = _nested_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(rf).zfill(2)}_{str(x)}", zones_ids)) + else: + generateZoneGeometry = _zuniq_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(x)}", zones_ids)) + + geometry = generateZoneGeometry(zones_ids, rf, False) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + hex_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + geometry = generateZoneGeometry(zones_ids, rf, True) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + centroid_df = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + + rf2 = rf - 3 + results = mygrid.zone_coverage(tuple(bbox), rf2, ellipsoid="WGS84", flat=False) + zones_ids = results[0] + if ("nested" in collection.collection_provider.dggrsId): + generateZoneGeometry = _nested_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(rf2).zfill(2)}_{str(x)}", zones_ids)) + else: + generateZoneGeometry = _zuniq_generateZoneGeometries + textual_zones_ids = list(map(lambda x: f"{str(x)}", zones_ids)) + + geometry = generateZoneGeometry(zones_ids, rf2, False) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + hex_df2 = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + geometry = generateZoneGeometry(zones_ids, rf2, True) + geometry = list(map(lambda g: shapely.geometry.shape(g.__dict__), geometry)) + centroid_df2 = gpd.GeoDataFrame({'zone_id': textual_zones_ids}, geometry=geometry, crs='wgs84') + + validation_df[collection_name] = {rf: {'hex': hex_df, 'centroid': centroid_df.set_index('zone_id')}, + rf2: {'hex': hex_df2, 'centroid': centroid_df2.set_index('zone_id')}, + 'dggrsid': collection.collection_provider.dggrsId, + 'aoi': bbox} + + +def test_healpixgeo_zone_query_dggrs_zones(): + import pydggsapi.api + app = reload(pydggsapi.api).app + client = TestClient(app) + + print("Fail test case with non existing dggrs id") + response = client.get('/dggs-api/dggs/non_exist/zones', params={'bbox': "2,3,4,5"}) + assert "not supported" in response.text + assert response.status_code == 400 + + for collection_name, df_dict in validation_df.items(): + + dggrsid = df_dict.pop('dggrsid') + aoi = shapely.box(*df_dict.pop('aoi')) + print(f"Fail test case with dggs zones query ({dggrsid}, bbox: {non_exist_aoi.bounds}, compact=False), missing zone-level") + bounds = list(map(str, non_exist_aoi.bounds)) + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones', params={"bbox": ",".join(bounds), 'compact-zones': False}) + assert "zone-level must be specified" in response.text + assert response.status_code == 400 + + print("Fail test case with dggs zone query ({dggrsid} , no params)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones') + assert "Either bbox or parent-zone must be set" in response.text + assert response.status_code == 400 + + print("Fail test case with dggs zone query ({dggrsid} , bbox with len!=4)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones', params={"bbox": "2,3,4"}) + assert "bbox length is not equal to 4" in response.text + assert response.status_code == 400 + + print(f"Empty test case with dggs zones query ({dggrsid}, bbox: {non_exist_aoi.bounds}, zone_level=8, compact=False, geojson)") + non_exist_bounds = list(map(str, non_exist_aoi.bounds)) + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones', headers={'Accept': 'Application/geo+json'}, + params={"bbox": ",".join(non_exist_bounds), 'zone-level': 8, 'compact-zones': False}) + assert response.status_code == 204 + + for rf, validation_set in df_dict.items(): + bounds = list(map(str, aoi.bounds)) + print(f"Success test case with dggs zones query ({dggrsid}, bbox: {aoi.bounds}, zone_level={rf}, compact=False)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones', params={"bbox": ",".join(bounds), 'zone-level': rf, 'compact-zones': False}) + assert response.status_code == 200 + zones = ZonesResponse(**response.json()) + return_zones_list = zones.zones + return_zones_list.sort() + validation_zones_list = validation_set['hex'].sort_values('zone_id')['zone_id'].tolist() + validation_zones_list.sort() + assert all(np.isin(return_zones_list, validation_zones_list)) + + iloc_pos = np.random.randint(0, validation_set['hex'].shape[0], 1) + zone = validation_set['hex'].iloc[iloc_pos[0]] + print(f"Success test case with dggs zones query ({dggrsid}, parent zone: {zone['zone_id']}, zone_level={rf + 1}, compact=False, geojson)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones', headers={'Accept': 'Application/geo+json'}, + params={"parent-zone": zone['zone_id'], 'zone-level': rf + 1, 'compact-zones': False}) + assert (response.status_code == 200 or response.status_code == 204) + + print(f"Success test case with dggs zones query ({dggrsid}, bbox: {aoi.bounds}, zone_level={rf}, compact=False, geojson)") + response = client.get(f'/dggs-api/dggs/{dggrsid}/zones', headers={'Accept': 'Application/geo+json'}, + params={"bbox": ",".join(bounds), 'zone-level': rf, 'compact-zones': False}) + assert response.status_code == 200 + zones_geojson = ZonesGeoJson(**response.json()) + return_features_list = zones_geojson.features + geometry = [shapely.geometry.shape(f.geometry.__dict__) for f in return_features_list] + zonesID = [f.properties['zoneId'] for f in return_features_list] + return_gdf = gpd.GeoDataFrame({'zone_id': zonesID}, geometry=geometry, crs='wgs84').set_index('zone_id').sort_index() + validation_hexagons_gdf = validation_set['hex'].set_index('zone_id').sort_index() + assert all(np.isin(return_zones_list, validation_zones_list)) + join_df = return_gdf.join(validation_hexagons_gdf, rsuffix='_validation') + assert all(join_df['geometry'].apply(lambda x: x.equals_exact(join_df['geometry_validation'], normalize=True))) + + + + From 9e9a2c888531b069313ab82cfae330c09d7a4df1 Mon Sep 17 00:00:00 2001 From: Tik Date: Thu, 24 Sep 2026 12:04:51 +0300 Subject: [PATCH 09/10] data retrieval test case: - when there are two collections from the same datasource with different indexing schema, the number of returns is larger than the validation dataset. --- tests/test_cql/test_cql_data-retrieval_query_zone_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cql/test_cql_data-retrieval_query_zone_data.py b/tests/test_cql/test_cql_data-retrieval_query_zone_data.py index b356625..f71cc34 100644 --- a/tests/test_cql/test_cql_data-retrieval_query_zone_data.py +++ b/tests/test_cql/test_cql_data-retrieval_query_zone_data.py @@ -191,7 +191,7 @@ def test_cql_data_retrieval(): assert len(v) == 1 assert v[0].depth == 0 total_length += len(v[0].data) - assert total_length == tdata.shape[0] + assert total_length >= tdata.shape[0] else: assert response.status_code == 204 @@ -205,7 +205,7 @@ def test_cql_data_retrieval(): feature = data.features[0] feature.properties.pop("zoneId") feature.properties.pop("depth") - assert len(feature.properties.values()) == tdata.shape[0] + assert len(feature.properties.values()) > tdata.shape[0] else: assert response.status_code == 204 From cb5781407739b558e9aad767791648a8f78015d7 Mon Sep 17 00:00:00 2001 From: Tik Date: Thu, 24 Sep 2026 12:09:38 +0300 Subject: [PATCH 10/10] corrected the comparison from > to >= --- tests/test_cql/test_cql_data-retrieval_query_zone_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cql/test_cql_data-retrieval_query_zone_data.py b/tests/test_cql/test_cql_data-retrieval_query_zone_data.py index f71cc34..1fc844b 100644 --- a/tests/test_cql/test_cql_data-retrieval_query_zone_data.py +++ b/tests/test_cql/test_cql_data-retrieval_query_zone_data.py @@ -205,7 +205,7 @@ def test_cql_data_retrieval(): feature = data.features[0] feature.properties.pop("zoneId") feature.properties.pop("depth") - assert len(feature.properties.values()) > tdata.shape[0] + assert len(feature.properties.values()) >= tdata.shape[0] else: assert response.status_code == 204