Skip to content

Grids API

earthcatalog.grids.h3_partitioner

H3 hexagonal grid partitioner.

Uses Uber's H3 <https://h3geo.org/>_ library to map geometries to hexagonal grid cells at a configurable resolution.

Resolution guide

Resolution Avg. cell area Global cells Recommended use
0 ~4,250,000 km² 122 Continental-scale
1 ~607,220 km² 842 Production default
2 ~86,750 km² 5,882 Sub-regional granularity
3 ~12,390 km² 41,162 Dense urban datasets

Boundary-inclusive contract

A polygon whose edge passes through a cell — but whose interior does not contain that cell's centroid — is still assigned to the edge cell. :class:H3Partitioner achieves this by combining:

  1. h3.geo_to_cells() — cells whose center falls inside the polygon.
  2. A densified boundary walk (_boundary_cells) — cells touched by the polygon's exterior ring sampled at ~10 km spacing.

This guarantees no data gap at cell boundaries regardless of item shape.

Classes

H3Partitioner

Bases: AbstractPartitioner

Returns every H3 cell that has ANY overlap with the input geometry: - Polygon/MultiPolygon: cells whose center falls inside (h3.geo_to_cells) UNION cells touched by the boundary (densified ring walk) - Point: single cell containing the point (h3.latlng_to_cell)

Source code in earthcatalog/grids/h3_partitioner.py
class H3Partitioner(AbstractPartitioner):
    """
    Returns every H3 cell that has ANY overlap with the input geometry:
    - Polygon/MultiPolygon: cells whose center falls inside (h3.geo_to_cells)
      UNION cells touched by the boundary (densified ring walk)
    - Point: single cell containing the point (h3.latlng_to_cell)
    """

    def __init__(self, resolution: int = 3) -> None:
        self.resolution = resolution

    def get_intersecting_keys(self, geom_wkb: bytes) -> list[str]:
        geom = wkb.loads(geom_wkb)

        if geom.geom_type == "Point":
            lon, lat = geom.x, geom.y
            return [h3.latlng_to_cell(lat, lon, self.resolution)]

        interior = set(h3.geo_to_cells(mapping(geom), self.resolution))
        boundary = _boundary_cells(geom, self.resolution)
        return list(interior | boundary)

earthcatalog.grids.geojson_partitioner

GeoJSON partitioner using an R-tree (STRtree) for fast intersection lookups.

Supports arbitrary polygon boundaries (drainage basins, polar regions, etc.). The boundaries file can be a local path or an s3:// URI; both are read via obstore so there is no dependency on requests/s3fs/boto3.

Classes

GeoJSONPartitioner

Bases: AbstractPartitioner

Partitions geometries against a set of named polygon boundaries loaded from a GeoJSON FeatureCollection. Uses an STRtree for O(log n) lookups.

Parameters

boundaries_path: Local path or s3:// URI to a GeoJSON FeatureCollection. id_field: The GeoJSON feature property to use as the partition key string.

Source code in earthcatalog/grids/geojson_partitioner.py
class GeoJSONPartitioner(AbstractPartitioner):
    """
    Partitions geometries against a set of named polygon boundaries loaded
    from a GeoJSON FeatureCollection.  Uses an STRtree for O(log n) lookups.

    Parameters
    ----------
    boundaries_path:
        Local path or ``s3://`` URI to a GeoJSON FeatureCollection.
    id_field:
        The GeoJSON feature property to use as the partition key string.
    """

    def __init__(self, boundaries_path: str, id_field: str = "id") -> None:
        raw = _load_bytes(boundaries_path)
        features = json.loads(raw)["features"]

        self._geometries = [shape(feat["geometry"]) for feat in features]
        self._keys = [str(feat["properties"][id_field]) for feat in features]
        self._tree = STRtree(self._geometries)

    def get_intersecting_keys(self, geom_wkb: bytes) -> list[str]:
        geom = wkb.loads(geom_wkb)
        candidate_idxs = self._tree.query(geom, predicate="intersects")
        return [self._keys[i] for i in candidate_idxs]