Python bindings for H3, a hierarchical hexagonal geospatial indexing system
84
Specialized functions for advanced H3 operations including local coordinate systems, vertex operations, and system introspection. These functions enable sophisticated spatial analysis and provide access to H3's internal geometric structures.
Convert between H3 cells and local (i,j) coordinate systems for efficient spatial operations.
def cell_to_local_ij(origin: str, h: str) -> tuple[int, int]:
"""
Convert H3 cell to local (i,j) coordinates relative to an origin cell.
The local coordinate system is anchored to the origin cell's base cell,
not the origin cell itself. This provides consistent coordinates across
operations without recomputing the origin's position.
Args:
origin: Origin H3 cell identifier defining the coordinate system
h: Target H3 cell identifier to convert to local coordinates
Returns:
Tuple of (i, j) integer coordinates
Raises:
H3CellInvalidError: If either cell identifier is invalid
H3ResMismatchError: If cells have different resolutions
H3GridNavigationError: If cells are too far apart
Note:
Both cells must be at the same resolution.
The (0,0) coordinate represents the center of the base cell
containing the origin, not the origin cell itself.
"""
def local_ij_to_cell(origin: str, i: int, j: int) -> str:
"""
Convert local (i,j) coordinates to H3 cell relative to an origin cell.
This is the inverse operation of cell_to_local_ij().
Args:
origin: Origin H3 cell identifier defining the coordinate system
i: Integer i-coordinate
j: Integer j-coordinate
Returns:
H3 cell identifier at the specified local coordinates
Raises:
H3CellInvalidError: If origin is not a valid H3 cell
H3GridNavigationError: If coordinates are outside valid range
Note:
The coordinate system is anchored to the origin cell's base cell.
Large coordinate values may be outside the valid range.
"""Work with H3 cell vertices and their geometric properties.
def cell_to_vertex(h: str, vertex_num: int) -> str:
"""
Get a specific vertex of an H3 cell by vertex number.
Args:
h: H3 cell identifier
vertex_num: Vertex number (0-5 for hexagons, 0-4 for pentagons)
Returns:
H3 vertex identifier
Raises:
H3CellInvalidError: If h is not a valid H3 cell
ValueError: If vertex_num is outside valid range for the cell
Note:
Hexagons have vertices 0-5, pentagons have vertices 0-4.
Vertex numbering follows a consistent orientation.
"""
def cell_to_vertexes(h: str) -> list[str]:
"""
Get all vertices of an H3 cell.
Args:
h: H3 cell identifier
Returns:
List of H3 vertex identifiers (5 for pentagons, 6 for hexagons)
Raises:
H3CellInvalidError: If h is not a valid H3 cell
Note:
Vertices are returned in consistent order around the cell boundary.
Pentagon cells return 5 vertices, hexagon cells return 6 vertices.
"""
def vertex_to_latlng(v: str) -> tuple[float, float]:
"""
Get the latitude and longitude coordinates of an H3 vertex.
Args:
v: H3 vertex identifier
Returns:
Tuple of (latitude, longitude) in degrees
Raises:
H3VertexInvalidError: If v is not a valid H3 vertex
Note:
Vertices represent the corner points where cell boundaries meet.
Multiple cells may share the same vertex.
"""
def is_valid_vertex(v: str) -> bool:
"""
Check if an H3 vertex identifier is valid.
Args:
v: H3 vertex identifier (string or int)
Returns:
True if valid H3 vertex, False otherwise
Note:
Returns False for any input that cannot be parsed as H3 vertex,
including H3 cells or edges.
"""Access specialized cells and system structure information.
def get_pentagons(res: int) -> list[str]:
"""
Get all pentagon H3 cells at a given resolution.
Args:
res: H3 resolution (0-15)
Returns:
List of all 12 pentagon H3 cell identifiers at the specified resolution
Raises:
H3ResDomainError: If res < 0 or res > 15
Note:
There are exactly 12 pentagons at every resolution level.
Pentagons occur at the vertices of the underlying icosahedron.
Order is not guaranteed.
"""
def get_res0_cells() -> list[str]:
"""
Get all H3 cells at resolution 0 (base cells).
Returns:
List of all 122 base H3 cell identifiers
Note:
These are the coarsest cells in the H3 system.
Includes 12 pentagons and 110 hexagons.
All higher-resolution cells are descendants of these base cells.
Order is not guaranteed.
"""
def get_icosahedron_faces(h: str) -> set[int]:
"""
Get the icosahedron faces that intersect with an H3 cell.
H3 is based on an icosahedron projection, which has 20 triangular faces.
This function returns which faces are intersected by the given cell.
Args:
h: H3 cell identifier
Returns:
Python set of integers representing face numbers (0-19)
Raises:
H3CellInvalidError: If h is not a valid H3 cell
Note:
Most cells intersect 1 face, but cells near face boundaries
may intersect 2 or 3 faces. Face numbers range from 0 to 19.
"""import h3
# Set up a local coordinate system
origin = h3.latlng_to_cell(37.7749, -122.4194, 9) # San Francisco
print(f"Origin cell: {origin}")
# Get some nearby cells
nearby_cells = h3.grid_disk(origin, k=2)
print(f"Working with {len(nearby_cells)} nearby cells")
# Convert cells to local coordinates
local_coords = {}
for cell in nearby_cells:
i, j = h3.cell_to_local_ij(origin, cell)
local_coords[cell] = (i, j)
print(f"\nLocal coordinates (first 10 cells):")
for cell, (i, j) in list(local_coords.items())[:10]:
print(f" {cell}: ({i:3d}, {j:3d})")
# Find origin's coordinates (should be relative to its base cell, not (0,0))
origin_i, origin_j = h3.cell_to_local_ij(origin, origin)
print(f"\nOrigin coordinates: ({origin_i}, {origin_j})")
print("Note: Origin coordinates are relative to base cell center, not (0,0)")
# Test round-trip conversion
test_cell = list(nearby_cells)[5]
i, j = h3.cell_to_local_ij(origin, test_cell)
recovered_cell = h3.local_ij_to_cell(origin, i, j)
print(f"\nRound-trip test:")
print(f" Original: {test_cell}")
print(f" Local coords: ({i}, {j})")
print(f" Recovered: {recovered_cell}")
print(f" Match: {test_cell == recovered_cell}")import h3
import matplotlib.pyplot as plt
# Create a coordinate grid pattern
center = h3.latlng_to_cell(40.7589, -73.9851, 8) # NYC
region = h3.grid_disk(center, k=4)
# Convert to local coordinates for analysis
coords = []
cells = []
for cell in region:
i, j = h3.cell_to_local_ij(center, cell)
coords.append((i, j))
cells.append(cell)
print(f"Analyzing {len(region)} cells in local coordinate space")
# Find coordinate ranges
i_coords = [i for i, j in coords]
j_coords = [j for i, j in coords]
print(f"I coordinate range: {min(i_coords)} to {max(i_coords)}")
print(f"J coordinate range: {min(j_coords)} to {max(j_coords)}")
# Analyze coordinate distribution
i_span = max(i_coords) - min(i_coords)
j_span = max(j_coords) - min(j_coords)
print(f"Coordinate space spans: {i_span} x {j_span}")
# Find cells along coordinate axes
origin_i, origin_j = h3.cell_to_local_ij(center, center)
print(f"\nCells along coordinate axes (relative to origin at {origin_i}, {origin_j}):")
# Find cells with same i-coordinate as origin
same_i_cells = [(cell, i, j) for cell, (i, j) in zip(cells, coords) if i == origin_i]
print(f" Same I-coordinate ({origin_i}): {len(same_i_cells)} cells")
# Find cells with same j-coordinate as origin
same_j_cells = [(cell, i, j) for cell, (i, j) in zip(cells, coords) if j == origin_j]
print(f" Same J-coordinate ({origin_j}): {len(same_j_cells)} cells")
# Optional: Create a simple plot if matplotlib is available
try:
plt.figure(figsize=(10, 8))
plt.scatter(i_coords, j_coords, alpha=0.6)
plt.scatter([origin_i], [origin_j], color='red', s=100, label='Origin')
plt.xlabel('I Coordinate')
plt.ylabel('J Coordinate')
plt.title('H3 Cells in Local Coordinate Space')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.savefig('h3_local_coords.png', dpi=150, bbox_inches='tight')
print(f"\nCoordinate plot saved as 'h3_local_coords.png'")
except ImportError:
print(f"\nMatplotlib not available - skipping coordinate plot")import h3
# Analyze vertices for different cell types
test_cells = [
h3.latlng_to_cell(37.7749, -122.4194, 8), # Regular hexagon
h3.get_pentagons(8)[0] # Pentagon at same resolution
]
print("Vertex analysis:")
for i, cell in enumerate(test_cells):
cell_type = "pentagon" if h3.is_pentagon(cell) else "hexagon"
print(f"\nCell {i+1} ({cell_type}): {cell}")
# Get all vertices
vertices = h3.cell_to_vertexes(cell)
print(f" Vertices: {len(vertices)}")
# Show each vertex with coordinates
vertex_coords = []
for j, vertex in enumerate(vertices):
lat, lng = h3.vertex_to_latlng(vertex)
vertex_coords.append((lat, lng))
is_valid = h3.is_valid_vertex(vertex)
print(f" Vertex {j}: {vertex} -> {lat:.6f}, {lng:.6f} (valid: {is_valid})")
# Test individual vertex access
print(f" Individual vertex access:")
for vertex_num in range(len(vertices)):
individual_vertex = h3.cell_to_vertex(cell, vertex_num)
matches_list = individual_vertex == vertices[vertex_num]
print(f" Vertex {vertex_num}: {individual_vertex} (matches list: {matches_list})")
# Calculate vertex distances (perimeter)
perimeter = 0.0
for j in range(len(vertex_coords)):
start = vertex_coords[j]
end = vertex_coords[(j + 1) % len(vertex_coords)] # Wrap around
distance = h3.great_circle_distance(start, end, 'km')
perimeter += distance
print(f" Edge {j}-{(j+1) % len(vertex_coords)}: {distance:.6f} km")
print(f" Total perimeter: {perimeter:.6f} km")
# Compare to expected edge count
expected_vertices = 5 if h3.is_pentagon(cell) else 6
assert len(vertices) == expected_verticesimport h3
# Analyze pentagon distribution across resolutions
print("Pentagon analysis across resolutions:")
for res in range(0, 8):
pentagons = h3.get_pentagons(res)
print(f"\nResolution {res}: {len(pentagons)} pentagons")
# Analyze pentagon locations
pentagon_coords = []
for pentagon in pentagons:
lat, lng = h3.cell_to_latlng(pentagon)
pentagon_coords.append((lat, lng, pentagon))
# Sort by latitude to see distribution
pentagon_coords.sort(key=lambda x: x[0], reverse=True) # North to South
print(" Pentagon locations (North to South):")
for i, (lat, lng, pentagon) in enumerate(pentagon_coords[:5]): # Show first 5
base_cell = h3.get_base_cell_number(pentagon)
print(f" {i+1}: {pentagon} -> {lat:7.2f}°, {lng:8.2f}° (base cell {base_cell})")
if len(pentagon_coords) > 5:
print(f" ... and {len(pentagon_coords) - 5} more")
# Analyze pentagon neighbors
print(f"\nPentagon neighbor analysis at resolution 6:")
pentagon = h3.get_pentagons(6)[0]
neighbors = h3.grid_ring(pentagon, k=1)
print(f"Pentagon {pentagon}:")
print(f" Neighbors: {len(neighbors)} (expected 5 for pentagon)")
# Check if neighbors are hexagons
neighbor_types = {}
for neighbor in neighbors:
is_pent = h3.is_pentagon(neighbor)
neighbor_type = "pentagon" if is_pent else "hexagon"
neighbor_types[neighbor_type] = neighbor_types.get(neighbor_type, 0) + 1
print(f" Neighbor types: {dict(neighbor_types)}")import h3
# Analyze icosahedron face distribution
print("Icosahedron face analysis:")
# Test cells at different locations and resolutions
test_cases = [
("Equator", 0, 0, 5),
("North Pole region", 85, 0, 5),
("South Pole region", -85, 0, 5),
("Pacific", 0, -180, 5),
("Face boundary", 0, 0, 3), # Lower resolution to potentially cross faces
]
for name, lat, lng, res in test_cases:
cell = h3.latlng_to_cell(lat, lng, res)
faces = h3.get_icosahedron_faces(cell)
print(f"\n{name} (res {res}): {cell}")
print(f" Intersects faces: {sorted(faces)} ({len(faces)} faces)")
# Get neighbors and analyze their faces
neighbors = h3.grid_ring(cell, k=1)
neighbor_faces = set()
for neighbor in neighbors:
n_faces = h3.get_icosahedron_faces(neighbor)
neighbor_faces.update(n_faces)
all_faces = faces.union(neighbor_faces)
print(f" Cell + neighbors span faces: {sorted(all_faces)} ({len(all_faces)} total)")
# Find cells that cross multiple faces
print(f"\nSearching for multi-face cells at resolution 2:")
multi_face_count = 0
face_distribution = {}
# Sample some cells at low resolution
for base_cell in h3.get_res0_cells()[:20]: # Check first 20 base cells
children = h3.cell_to_children(base_cell, res=2)
for cell in children:
faces = h3.get_icosahedron_faces(cell)
face_count = len(faces)
face_distribution[face_count] = face_distribution.get(face_count, 0) + 1
if face_count > 1:
multi_face_count += 1
if multi_face_count <= 3: # Show first few examples
lat, lng = h3.cell_to_latlng(cell)
print(f" Multi-face cell: {cell} -> {sorted(faces)} at {lat:.2f}, {lng:.2f}")
print(f"\nFace intersection distribution (sample of {sum(face_distribution.values())} cells):")
for face_count, count in sorted(face_distribution.items()):
print(f" {face_count} face(s): {count} cells ({count/sum(face_distribution.values()):.1%})")import h3
# Comprehensive system structure analysis
print("H3 System Structure Overview:")
# Base cell analysis
base_cells = h3.get_res0_cells()
pentagons_res0 = h3.get_pentagons(0)
print(f"\nBase structure (Resolution 0):")
print(f" Total base cells: {len(base_cells)}")
print(f" Pentagons: {len(pentagons_res0)}")
print(f" Hexagons: {len(base_cells) - len(pentagons_res0)}")
# Verify pentagon consistency
pentagon_base_cells = [cell for cell in base_cells if h3.is_pentagon(cell)]
print(f" Pentagon count verification: {len(pentagon_base_cells)} (should match {len(pentagons_res0)})")
# Show base cell number range
base_numbers = [h3.get_base_cell_number(cell) for cell in base_cells]
print(f" Base cell numbers: {min(base_numbers)} to {max(base_numbers)}")
# Child count analysis
print(f"\nHierarchy analysis (Resolution 0 -> 1):")
hex_child_counts = []
pent_child_counts = []
for cell in base_cells[:10]: # Sample first 10 base cells
children = h3.cell_to_children(cell, res=1)
child_count = len(children)
if h3.is_pentagon(cell):
pent_child_counts.append(child_count)
else:
hex_child_counts.append(child_count)
if hex_child_counts:
print(f" Hexagon children: {hex_child_counts[0]} (sample)")
if pent_child_counts:
print(f" Pentagon children: {pent_child_counts[0]} (sample)")
# Face coverage analysis
print(f"\nIcosahedron face coverage:")
face_cells = {}
for face_num in range(20):
# Find base cells that intersect this face
face_base_cells = []
for cell in base_cells:
faces = h3.get_icosahedron_faces(cell)
if face_num in faces:
face_base_cells.append(cell)
face_cells[face_num] = len(face_base_cells)
face_counts = list(face_cells.values())
print(f" Cells per face - min: {min(face_counts)}, max: {max(face_counts)}, avg: {sum(face_counts)/len(face_counts):.1f}")
# System capacity
print(f"\nSystem capacity:")
for res in [0, 5, 10, 15]:
total_cells = h3.get_num_cells(res)
pentagons = 12 # Always 12 pentagons
hexagons = total_cells - pentagons
print(f" Resolution {res:2d}: {total_cells:15,} total ({hexagons:15,} hex + {pentagons:2,} pent)")
print(f"\nMaximum theoretical capacity: {h3.get_num_cells(15):,} cells at resolution 15")Install with Tessl CLI
npx tessl i tessl/pypi-h3docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10