CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-h3

Python bindings for H3, a hierarchical hexagonal geospatial indexing system

84

0.95x
Overview
Eval results
Files

polygon-operations.mddocs/

Polygon Operations

Functions for converting between H3 cells and geographic polygons, enabling coverage analysis and shape-based queries. These operations allow you to work with arbitrary geographic areas using H3's hexagonal grid system.

Capabilities

Shape to Cells Conversion

Convert geographic shapes to H3 cell collections that cover the shape area.

def h3shape_to_cells(h3shape: H3Shape, res: int) -> list[str]:
    """
    Convert an H3Shape to H3 cells that cover the shape area.
    
    Returns cells whose center points lie within the shape boundaries.
    Works with both LatLngPoly and LatLngMultiPoly shapes.
    
    Args:
        h3shape: H3Shape object (LatLngPoly or LatLngMultiPoly)
        res: Target resolution for output cells (0-15)
        
    Returns:
        List of H3 cell identifiers covering the shape area
        
    Raises:
        H3ResDomainError: If res < 0 or res > 15
        ValueError: If h3shape is not a valid H3Shape object
        
    Note:
        Uses center containment: cells are included if their center 
        point falls within the shape. Order is not guaranteed.
    """

def polygon_to_cells(h3shape: H3Shape, res: int) -> list[str]:
    """
    Alias for h3shape_to_cells().
    
    Maintained for backward compatibility.
    """

def h3shape_to_cells_experimental(
    h3shape: H3Shape, 
    res: int, 
    contain: str = 'center'
) -> list[str]:
    """
    Experimental shape to cells conversion with multiple containment modes.
    
    Args:
        h3shape: H3Shape object (LatLngPoly or LatLngMultiPoly)
        res: Target resolution for output cells (0-15)
        contain: Containment mode:
            - 'center': Cell center must be in shape (default, matches h3shape_to_cells)
            - 'full': Entire cell must be within shape  
            - 'overlap': Any part of cell overlaps with shape
            - 'bbox_overlap': Cell bounding box overlaps with shape
            
    Returns:
        List of H3 cell identifiers based on containment criteria
        
    Raises:
        H3ResDomainError: If res < 0 or res > 15
        ValueError: If h3shape or contain parameter is invalid
        
    Warning:
        This function is experimental and may change in future versions.
        Use h3shape_to_cells() for stable API.
    """

def polygon_to_cells_experimental(
    h3shape: H3Shape, 
    res: int, 
    contain: str = 'center'
) -> list[str]:
    """
    Alias for h3shape_to_cells_experimental().
    
    Maintained for backward compatibility.
    """

Cells to Shape Conversion

Convert H3 cell collections back to geographic shapes.

def cells_to_h3shape(cells: list[str], tight: bool = True) -> H3Shape:
    """
    Convert a collection of H3 cells to an H3Shape describing their coverage.
    
    Creates polygon boundaries that encompass the total area covered
    by the input cells.
    
    Args:
        cells: Collection of H3 cell identifiers
        tight: If True, return LatLngPoly when possible (single polygon).
               If False, always return LatLngMultiPoly.
               
    Returns:
        LatLngPoly if cells form a single connected area and tight=True,
        otherwise LatLngMultiPoly
        
    Raises:
        H3CellInvalidError: If any cell identifier is invalid
        
    Note:
        Input cells can be at different resolutions.
        Result includes holes where no cells are present within the coverage area.
    """

GeoJSON Integration

Convert between H3 cells and GeoJSON-compatible geographic data.

def geo_to_cells(geo: dict, res: int) -> list[str]:
    """
    Convert GeoJSON-like geometry to H3 cells.
    
    Accepts any object implementing __geo_interface__ or a dictionary
    in GeoJSON format.
    
    Args:
        geo: GeoJSON-like geometry object or dictionary with:
             - type: 'Polygon' or 'MultiPolygon'
             - coordinates: Coordinate arrays in GeoJSON format
        res: Target resolution for output cells (0-15)
        
    Returns:
        List of H3 cell identifiers covering the geometry
        
    Raises:
        H3ResDomainError: If res < 0 or res > 15
        ValueError: If geo is not valid GeoJSON format
        
    Note:
        Coordinates should be in [lng, lat] format (GeoJSON standard).
        Supports Polygon and MultiPolygon geometries.
    """

def cells_to_geo(cells: list[str], tight: bool = True) -> dict:
    """
    Convert H3 cells to GeoJSON-compatible dictionary.
    
    Args:
        cells: Collection of H3 cell identifiers
        tight: If True, return Polygon when possible.
               If False, always return MultiPolygon.
               
    Returns:
        Dictionary in GeoJSON format with:
        - type: 'Polygon' or 'MultiPolygon'
        - coordinates: Coordinate arrays in [lng, lat] format
        
    Raises:
        H3CellInvalidError: If any cell identifier is invalid
        
    Note:
        Output coordinates are in [lng, lat] format (GeoJSON standard).
        Includes holes where no cells are present within the coverage area.
    """

H3Shape Classes

Geographic shape classes for representing polygons with optional holes.

class H3Shape:
    """
    Abstract base class for geographic shapes.
    
    Provides __geo_interface__ property for GeoJSON compatibility.
    """

class LatLngPoly(H3Shape):
    """
    Polygon with optional holes, using lat/lng coordinates.
    
    Represents a single polygon with an outer boundary and optional holes.
    All coordinates are in (latitude, longitude) format.
    """
    
    def __init__(self, outer: list[tuple[float, float]], *holes: list[tuple[float, float]]):
        """
        Create a polygon from boundary rings.
        
        Args:
            outer: List of (lat, lng) points defining the outer boundary.
                   Must have at least 3 points. Ring is automatically closed.
            *holes: Optional hole boundaries, each as a list of (lat, lng) points.
                   Each hole must have at least 3 points.
                   
        Raises:
            ValueError: If any ring has fewer than 3 points or points are not 2D
            
        Note:
            Rings are automatically opened (duplicate first/last point removed).
            Points should be in counter-clockwise order for outer ring,
            clockwise for holes (though not enforced).
        """
    
    @property
    def outer(self) -> tuple[tuple[float, float], ...]:
        """Outer boundary ring as tuple of (lat, lng) points."""
        
    @property  
    def holes(self) -> tuple[tuple[tuple[float, float], ...], ...]:
        """Hole boundaries as tuple of rings, each ring is tuple of (lat, lng) points."""
        
    @property
    def loopcode(self) -> str:
        """
        Short description of polygon structure.
        
        Format: '[outer_count/(hole1_count, hole2_count, ...)]'
        Example: '[382/(18, 6, 6)]' means 382 points in outer ring,
        3 holes with 18, 6, and 6 points respectively.
        """

class LatLngMultiPoly(H3Shape):
    """
    Collection of multiple LatLngPoly polygons.
    
    Represents multiple disconnected polygons as a single shape.
    """
    
    def __init__(self, *polys: LatLngPoly):
        """
        Create a multipolygon from individual polygons.
        
        Args:
            *polys: LatLngPoly objects to combine
            
        Raises:
            ValueError: If any input is not a LatLngPoly object
        """
    
    @property
    def polys(self) -> tuple[LatLngPoly, ...]:
        """Individual polygons as tuple of LatLngPoly objects."""
        
    def __len__(self) -> int:
        """Number of individual polygons."""
        
    def __getitem__(self, index: int) -> LatLngPoly:
        """Get individual polygon by index."""
        
    def __iter__(self):
        """Iterate over individual polygons."""

Shape Utility Functions

def geo_to_h3shape(geo: dict) -> H3Shape:
    """
    Convert GeoJSON-like geometry to H3Shape object.
    
    Args:
        geo: Object implementing __geo_interface__ or GeoJSON dictionary
        
    Returns:
        LatLngPoly for Polygon geometry, LatLngMultiPoly for MultiPolygon
        
    Raises:
        ValueError: If geometry type is not supported or format is invalid
        
    Note:
        Input coordinates should be in [lng, lat] format (GeoJSON standard).
        Output uses (lat, lng) format in H3Shape objects.
    """

def h3shape_to_geo(h3shape: H3Shape) -> dict:
    """
    Convert H3Shape object to GeoJSON-compatible dictionary.
    
    Args:
        h3shape: LatLngPoly or LatLngMultiPoly object
        
    Returns:
        Dictionary in GeoJSON format with type and coordinates
        
    Note:
        Output coordinates are in [lng, lat] format (GeoJSON standard).
        Input H3Shape uses (lat, lng) format.
    """

Usage Examples

Basic Shape to Cells Conversion

import h3
from h3 import LatLngPoly

# Create a simple polygon around San Francisco
sf_polygon = LatLngPoly([
    (37.68, -122.54),   # Southwest corner
    (37.68, -122.34),   # Southeast corner  
    (37.82, -122.34),   # Northeast corner
    (37.82, -122.54)    # Northwest corner
])

print(f"Polygon: {sf_polygon.loopcode}")

# Convert to H3 cells at different resolutions
for resolution in [6, 7, 8, 9]:
    cells = h3.h3shape_to_cells(sf_polygon, resolution)
    print(f"Resolution {resolution}: {len(cells)} cells")

# Show cell density difference
cells_6 = h3.h3shape_to_cells(sf_polygon, 6)
cells_9 = h3.h3shape_to_cells(sf_polygon, 9)
print(f"Density increase (res 6->9): {len(cells_9) / len(cells_6):.1f}x")

Polygon with Holes

import h3
from h3 import LatLngPoly

# Create polygon with a hole (donut shape)
outer_ring = [
    (37.70, -122.50), (37.70, -122.40), 
    (37.80, -122.40), (37.80, -122.50)
]

hole_ring = [
    (37.73, -122.47), (37.73, -122.43),
    (37.77, -122.43), (37.77, -122.47)
]

donut_polygon = LatLngPoly(outer_ring, hole_ring)
print(f"Donut polygon: {donut_polygon.loopcode}")

# Convert to cells - cells in hole area should be excluded
cells = h3.h3shape_to_cells(donut_polygon, resolution=8)
print(f"Donut coverage: {len(cells)} cells")

# Compare to solid polygon (no hole)
solid_polygon = LatLngPoly(outer_ring)
solid_cells = h3.h3shape_to_cells(solid_polygon, resolution=8)
print(f"Solid coverage: {len(solid_cells)} cells")
print(f"Hole contains: {len(solid_cells) - len(cells)} cells")

Cells to Shape Conversion

import h3

# Start with a region of cells
center = h3.latlng_to_cell(40.7589, -73.9851, 8)  # NYC
region_cells = h3.grid_disk(center, k=3)

print(f"Original: {len(region_cells)} cells")

# Convert cells back to shape
shape = h3.cells_to_h3shape(region_cells, tight=True)
print(f"Shape type: {type(shape).__name__}")
print(f"Shape: {shape}")

# Convert shape back to cells and verify coverage
recovered_cells = h3.h3shape_to_cells(shape, resolution=8)
print(f"Recovered: {len(recovered_cells)} cells")

# Check if we recovered the same cells
original_set = set(region_cells)
recovered_set = set(recovered_cells)
print(f"Perfect recovery: {original_set == recovered_set}")
print(f"Coverage ratio: {len(recovered_set & original_set) / len(original_set):.3f}")

GeoJSON Integration

import h3
import json

# Example GeoJSON polygon (coordinates in [lng, lat] format)
geojson_polygon = {
    "type": "Polygon",
    "coordinates": [[
        [-122.54, 37.68], [-122.34, 37.68],  # Note: [lng, lat] format
        [-122.34, 37.82], [-122.54, 37.82],
        [-122.54, 37.68]  # Closed ring
    ]]
}

# Convert GeoJSON to H3 cells
cells = h3.geo_to_cells(geojson_polygon, resolution=7)
print(f"GeoJSON polygon -> {len(cells)} H3 cells")

# Convert cells back to GeoJSON
recovered_geojson = h3.cells_to_geo(cells, tight=True)
print(f"Recovered GeoJSON type: {recovered_geojson['type']}")
print(f"Coordinate rings: {len(recovered_geojson['coordinates'])}")

# Save as proper GeoJSON file
output_geojson = {
    "type": "Feature",
    "properties": {"name": "H3 Coverage"},
    "geometry": recovered_geojson
}

with open('h3_coverage.geojson', 'w') as f:
    json.dump(output_geojson, f, indent=2)

print("Saved coverage to h3_coverage.geojson")

Experimental Containment Modes

import h3
from h3 import LatLngPoly

# Create a small test polygon
test_polygon = LatLngPoly([
    (37.75, -122.45), (37.75, -122.44),
    (37.76, -122.44), (37.76, -122.45)
])

resolution = 10  # High resolution for detailed comparison

# Compare different containment modes
modes = ['center', 'full', 'overlap', 'bbox_overlap']
results = {}

for mode in modes:
    try:
        cells = h3.h3shape_to_cells_experimental(test_polygon, resolution, contain=mode)
        results[mode] = len(cells)
        print(f"{mode:12}: {len(cells)} cells")
    except Exception as e:
        print(f"{mode:12}: Error - {e}")

# Typically: full <= center <= overlap <= bbox_overlap
print(f"\nExpected order: full <= center <= overlap <= bbox_overlap")

MultiPolygon Handling

import h3
from h3 import LatLngPoly, LatLngMultiPoly

# Create separate polygons (islands)
island1 = LatLngPoly([
    (37.70, -122.50), (37.70, -122.45),
    (37.75, -122.45), (37.75, -122.50)
])

island2 = LatLngPoly([
    (37.77, -122.47), (37.77, -122.42),
    (37.82, -122.42), (37.82, -122.47)
])

# Combine into multipolygon
archipelago = LatLngMultiPoly(island1, island2)
print(f"Archipelago: {len(archipelago)} islands")
print(f"Description: {archipelago}")

# Convert multipolygon to cells
all_cells = h3.h3shape_to_cells(archipelago, resolution=8)
print(f"Total coverage: {len(all_cells)} cells")

# Convert individual islands for comparison
island1_cells = h3.h3shape_to_cells(island1, resolution=8)
island2_cells = h3.h3shape_to_cells(island2, resolution=8)

print(f"Island 1: {len(island1_cells)} cells")
print(f"Island 2: {len(island2_cells)} cells")
print(f"Sum matches total: {len(island1_cells) + len(island2_cells) == len(all_cells)}")

# Convert back to shape (should remain multipolygon)
recovered_shape = h3.cells_to_h3shape(all_cells, tight=False)  # Force multipolygon
print(f"Recovered type: {type(recovered_shape).__name__}")
print(f"Recovered islands: {len(recovered_shape)}")

Large Area Processing

import h3
from h3 import LatLngPoly
import time

# Create a large polygon (rough outline of California)
california = LatLngPoly([
    (42.0, -124.4), (32.5, -124.4), (32.5, -114.1),
    (35.0, -114.1), (36.0, -120.0), (42.0, -120.0)
])

print("Processing large area (California outline):")

# Process at different resolutions
for resolution in range(3, 8):
    start_time = time.time()
    cells = h3.h3shape_to_cells(california, resolution)
    elapsed = time.time() - start_time
    
    print(f"Resolution {resolution}: {len(cells):,} cells in {elapsed:.2f}s")
    
    # Estimate coverage area (very rough)
    avg_area = h3.average_hexagon_area(resolution, 'km^2')
    total_area = len(cells) * avg_area
    print(f"  Estimated area: {total_area:,.0f} km²")

# Note: Higher resolutions may take significant time and memory

Install with Tessl CLI

npx tessl i tessl/pypi-h3

docs

advanced-operations.md

cell-hierarchy.md

core-cell-operations.md

directed-edges.md

grid-navigation.md

index.md

measurements.md

polygon-operations.md

tile.json