0
# Polygon Operations
1
2
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.
3
4
## Capabilities
5
6
### Shape to Cells Conversion
7
8
Convert geographic shapes to H3 cell collections that cover the shape area.
9
10
```python { .api }
11
def h3shape_to_cells(h3shape: H3Shape, res: int) -> list[str]:
12
"""
13
Convert an H3Shape to H3 cells that cover the shape area.
14
15
Returns cells whose center points lie within the shape boundaries.
16
Works with both LatLngPoly and LatLngMultiPoly shapes.
17
18
Args:
19
h3shape: H3Shape object (LatLngPoly or LatLngMultiPoly)
20
res: Target resolution for output cells (0-15)
21
22
Returns:
23
List of H3 cell identifiers covering the shape area
24
25
Raises:
26
H3ResDomainError: If res < 0 or res > 15
27
ValueError: If h3shape is not a valid H3Shape object
28
29
Note:
30
Uses center containment: cells are included if their center
31
point falls within the shape. Order is not guaranteed.
32
"""
33
34
def polygon_to_cells(h3shape: H3Shape, res: int) -> list[str]:
35
"""
36
Alias for h3shape_to_cells().
37
38
Maintained for backward compatibility.
39
"""
40
41
def h3shape_to_cells_experimental(
42
h3shape: H3Shape,
43
res: int,
44
contain: str = 'center'
45
) -> list[str]:
46
"""
47
Experimental shape to cells conversion with multiple containment modes.
48
49
Args:
50
h3shape: H3Shape object (LatLngPoly or LatLngMultiPoly)
51
res: Target resolution for output cells (0-15)
52
contain: Containment mode:
53
- 'center': Cell center must be in shape (default, matches h3shape_to_cells)
54
- 'full': Entire cell must be within shape
55
- 'overlap': Any part of cell overlaps with shape
56
- 'bbox_overlap': Cell bounding box overlaps with shape
57
58
Returns:
59
List of H3 cell identifiers based on containment criteria
60
61
Raises:
62
H3ResDomainError: If res < 0 or res > 15
63
ValueError: If h3shape or contain parameter is invalid
64
65
Warning:
66
This function is experimental and may change in future versions.
67
Use h3shape_to_cells() for stable API.
68
"""
69
70
def polygon_to_cells_experimental(
71
h3shape: H3Shape,
72
res: int,
73
contain: str = 'center'
74
) -> list[str]:
75
"""
76
Alias for h3shape_to_cells_experimental().
77
78
Maintained for backward compatibility.
79
"""
80
```
81
82
### Cells to Shape Conversion
83
84
Convert H3 cell collections back to geographic shapes.
85
86
```python { .api }
87
def cells_to_h3shape(cells: list[str], tight: bool = True) -> H3Shape:
88
"""
89
Convert a collection of H3 cells to an H3Shape describing their coverage.
90
91
Creates polygon boundaries that encompass the total area covered
92
by the input cells.
93
94
Args:
95
cells: Collection of H3 cell identifiers
96
tight: If True, return LatLngPoly when possible (single polygon).
97
If False, always return LatLngMultiPoly.
98
99
Returns:
100
LatLngPoly if cells form a single connected area and tight=True,
101
otherwise LatLngMultiPoly
102
103
Raises:
104
H3CellInvalidError: If any cell identifier is invalid
105
106
Note:
107
Input cells can be at different resolutions.
108
Result includes holes where no cells are present within the coverage area.
109
"""
110
```
111
112
### GeoJSON Integration
113
114
Convert between H3 cells and GeoJSON-compatible geographic data.
115
116
```python { .api }
117
def geo_to_cells(geo: dict, res: int) -> list[str]:
118
"""
119
Convert GeoJSON-like geometry to H3 cells.
120
121
Accepts any object implementing __geo_interface__ or a dictionary
122
in GeoJSON format.
123
124
Args:
125
geo: GeoJSON-like geometry object or dictionary with:
126
- type: 'Polygon' or 'MultiPolygon'
127
- coordinates: Coordinate arrays in GeoJSON format
128
res: Target resolution for output cells (0-15)
129
130
Returns:
131
List of H3 cell identifiers covering the geometry
132
133
Raises:
134
H3ResDomainError: If res < 0 or res > 15
135
ValueError: If geo is not valid GeoJSON format
136
137
Note:
138
Coordinates should be in [lng, lat] format (GeoJSON standard).
139
Supports Polygon and MultiPolygon geometries.
140
"""
141
142
def cells_to_geo(cells: list[str], tight: bool = True) -> dict:
143
"""
144
Convert H3 cells to GeoJSON-compatible dictionary.
145
146
Args:
147
cells: Collection of H3 cell identifiers
148
tight: If True, return Polygon when possible.
149
If False, always return MultiPolygon.
150
151
Returns:
152
Dictionary in GeoJSON format with:
153
- type: 'Polygon' or 'MultiPolygon'
154
- coordinates: Coordinate arrays in [lng, lat] format
155
156
Raises:
157
H3CellInvalidError: If any cell identifier is invalid
158
159
Note:
160
Output coordinates are in [lng, lat] format (GeoJSON standard).
161
Includes holes where no cells are present within the coverage area.
162
"""
163
```
164
165
## H3Shape Classes
166
167
Geographic shape classes for representing polygons with optional holes.
168
169
```python { .api }
170
class H3Shape:
171
"""
172
Abstract base class for geographic shapes.
173
174
Provides __geo_interface__ property for GeoJSON compatibility.
175
"""
176
177
class LatLngPoly(H3Shape):
178
"""
179
Polygon with optional holes, using lat/lng coordinates.
180
181
Represents a single polygon with an outer boundary and optional holes.
182
All coordinates are in (latitude, longitude) format.
183
"""
184
185
def __init__(self, outer: list[tuple[float, float]], *holes: list[tuple[float, float]]):
186
"""
187
Create a polygon from boundary rings.
188
189
Args:
190
outer: List of (lat, lng) points defining the outer boundary.
191
Must have at least 3 points. Ring is automatically closed.
192
*holes: Optional hole boundaries, each as a list of (lat, lng) points.
193
Each hole must have at least 3 points.
194
195
Raises:
196
ValueError: If any ring has fewer than 3 points or points are not 2D
197
198
Note:
199
Rings are automatically opened (duplicate first/last point removed).
200
Points should be in counter-clockwise order for outer ring,
201
clockwise for holes (though not enforced).
202
"""
203
204
@property
205
def outer(self) -> tuple[tuple[float, float], ...]:
206
"""Outer boundary ring as tuple of (lat, lng) points."""
207
208
@property
209
def holes(self) -> tuple[tuple[tuple[float, float], ...], ...]:
210
"""Hole boundaries as tuple of rings, each ring is tuple of (lat, lng) points."""
211
212
@property
213
def loopcode(self) -> str:
214
"""
215
Short description of polygon structure.
216
217
Format: '[outer_count/(hole1_count, hole2_count, ...)]'
218
Example: '[382/(18, 6, 6)]' means 382 points in outer ring,
219
3 holes with 18, 6, and 6 points respectively.
220
"""
221
222
class LatLngMultiPoly(H3Shape):
223
"""
224
Collection of multiple LatLngPoly polygons.
225
226
Represents multiple disconnected polygons as a single shape.
227
"""
228
229
def __init__(self, *polys: LatLngPoly):
230
"""
231
Create a multipolygon from individual polygons.
232
233
Args:
234
*polys: LatLngPoly objects to combine
235
236
Raises:
237
ValueError: If any input is not a LatLngPoly object
238
"""
239
240
@property
241
def polys(self) -> tuple[LatLngPoly, ...]:
242
"""Individual polygons as tuple of LatLngPoly objects."""
243
244
def __len__(self) -> int:
245
"""Number of individual polygons."""
246
247
def __getitem__(self, index: int) -> LatLngPoly:
248
"""Get individual polygon by index."""
249
250
def __iter__(self):
251
"""Iterate over individual polygons."""
252
```
253
254
### Shape Utility Functions
255
256
```python { .api }
257
def geo_to_h3shape(geo: dict) -> H3Shape:
258
"""
259
Convert GeoJSON-like geometry to H3Shape object.
260
261
Args:
262
geo: Object implementing __geo_interface__ or GeoJSON dictionary
263
264
Returns:
265
LatLngPoly for Polygon geometry, LatLngMultiPoly for MultiPolygon
266
267
Raises:
268
ValueError: If geometry type is not supported or format is invalid
269
270
Note:
271
Input coordinates should be in [lng, lat] format (GeoJSON standard).
272
Output uses (lat, lng) format in H3Shape objects.
273
"""
274
275
def h3shape_to_geo(h3shape: H3Shape) -> dict:
276
"""
277
Convert H3Shape object to GeoJSON-compatible dictionary.
278
279
Args:
280
h3shape: LatLngPoly or LatLngMultiPoly object
281
282
Returns:
283
Dictionary in GeoJSON format with type and coordinates
284
285
Note:
286
Output coordinates are in [lng, lat] format (GeoJSON standard).
287
Input H3Shape uses (lat, lng) format.
288
"""
289
```
290
291
## Usage Examples
292
293
### Basic Shape to Cells Conversion
294
295
```python
296
import h3
297
from h3 import LatLngPoly
298
299
# Create a simple polygon around San Francisco
300
sf_polygon = LatLngPoly([
301
(37.68, -122.54), # Southwest corner
302
(37.68, -122.34), # Southeast corner
303
(37.82, -122.34), # Northeast corner
304
(37.82, -122.54) # Northwest corner
305
])
306
307
print(f"Polygon: {sf_polygon.loopcode}")
308
309
# Convert to H3 cells at different resolutions
310
for resolution in [6, 7, 8, 9]:
311
cells = h3.h3shape_to_cells(sf_polygon, resolution)
312
print(f"Resolution {resolution}: {len(cells)} cells")
313
314
# Show cell density difference
315
cells_6 = h3.h3shape_to_cells(sf_polygon, 6)
316
cells_9 = h3.h3shape_to_cells(sf_polygon, 9)
317
print(f"Density increase (res 6->9): {len(cells_9) / len(cells_6):.1f}x")
318
```
319
320
### Polygon with Holes
321
322
```python
323
import h3
324
from h3 import LatLngPoly
325
326
# Create polygon with a hole (donut shape)
327
outer_ring = [
328
(37.70, -122.50), (37.70, -122.40),
329
(37.80, -122.40), (37.80, -122.50)
330
]
331
332
hole_ring = [
333
(37.73, -122.47), (37.73, -122.43),
334
(37.77, -122.43), (37.77, -122.47)
335
]
336
337
donut_polygon = LatLngPoly(outer_ring, hole_ring)
338
print(f"Donut polygon: {donut_polygon.loopcode}")
339
340
# Convert to cells - cells in hole area should be excluded
341
cells = h3.h3shape_to_cells(donut_polygon, resolution=8)
342
print(f"Donut coverage: {len(cells)} cells")
343
344
# Compare to solid polygon (no hole)
345
solid_polygon = LatLngPoly(outer_ring)
346
solid_cells = h3.h3shape_to_cells(solid_polygon, resolution=8)
347
print(f"Solid coverage: {len(solid_cells)} cells")
348
print(f"Hole contains: {len(solid_cells) - len(cells)} cells")
349
```
350
351
### Cells to Shape Conversion
352
353
```python
354
import h3
355
356
# Start with a region of cells
357
center = h3.latlng_to_cell(40.7589, -73.9851, 8) # NYC
358
region_cells = h3.grid_disk(center, k=3)
359
360
print(f"Original: {len(region_cells)} cells")
361
362
# Convert cells back to shape
363
shape = h3.cells_to_h3shape(region_cells, tight=True)
364
print(f"Shape type: {type(shape).__name__}")
365
print(f"Shape: {shape}")
366
367
# Convert shape back to cells and verify coverage
368
recovered_cells = h3.h3shape_to_cells(shape, resolution=8)
369
print(f"Recovered: {len(recovered_cells)} cells")
370
371
# Check if we recovered the same cells
372
original_set = set(region_cells)
373
recovered_set = set(recovered_cells)
374
print(f"Perfect recovery: {original_set == recovered_set}")
375
print(f"Coverage ratio: {len(recovered_set & original_set) / len(original_set):.3f}")
376
```
377
378
### GeoJSON Integration
379
380
```python
381
import h3
382
import json
383
384
# Example GeoJSON polygon (coordinates in [lng, lat] format)
385
geojson_polygon = {
386
"type": "Polygon",
387
"coordinates": [[
388
[-122.54, 37.68], [-122.34, 37.68], # Note: [lng, lat] format
389
[-122.34, 37.82], [-122.54, 37.82],
390
[-122.54, 37.68] # Closed ring
391
]]
392
}
393
394
# Convert GeoJSON to H3 cells
395
cells = h3.geo_to_cells(geojson_polygon, resolution=7)
396
print(f"GeoJSON polygon -> {len(cells)} H3 cells")
397
398
# Convert cells back to GeoJSON
399
recovered_geojson = h3.cells_to_geo(cells, tight=True)
400
print(f"Recovered GeoJSON type: {recovered_geojson['type']}")
401
print(f"Coordinate rings: {len(recovered_geojson['coordinates'])}")
402
403
# Save as proper GeoJSON file
404
output_geojson = {
405
"type": "Feature",
406
"properties": {"name": "H3 Coverage"},
407
"geometry": recovered_geojson
408
}
409
410
with open('h3_coverage.geojson', 'w') as f:
411
json.dump(output_geojson, f, indent=2)
412
413
print("Saved coverage to h3_coverage.geojson")
414
```
415
416
### Experimental Containment Modes
417
418
```python
419
import h3
420
from h3 import LatLngPoly
421
422
# Create a small test polygon
423
test_polygon = LatLngPoly([
424
(37.75, -122.45), (37.75, -122.44),
425
(37.76, -122.44), (37.76, -122.45)
426
])
427
428
resolution = 10 # High resolution for detailed comparison
429
430
# Compare different containment modes
431
modes = ['center', 'full', 'overlap', 'bbox_overlap']
432
results = {}
433
434
for mode in modes:
435
try:
436
cells = h3.h3shape_to_cells_experimental(test_polygon, resolution, contain=mode)
437
results[mode] = len(cells)
438
print(f"{mode:12}: {len(cells)} cells")
439
except Exception as e:
440
print(f"{mode:12}: Error - {e}")
441
442
# Typically: full <= center <= overlap <= bbox_overlap
443
print(f"\nExpected order: full <= center <= overlap <= bbox_overlap")
444
```
445
446
### MultiPolygon Handling
447
448
```python
449
import h3
450
from h3 import LatLngPoly, LatLngMultiPoly
451
452
# Create separate polygons (islands)
453
island1 = LatLngPoly([
454
(37.70, -122.50), (37.70, -122.45),
455
(37.75, -122.45), (37.75, -122.50)
456
])
457
458
island2 = LatLngPoly([
459
(37.77, -122.47), (37.77, -122.42),
460
(37.82, -122.42), (37.82, -122.47)
461
])
462
463
# Combine into multipolygon
464
archipelago = LatLngMultiPoly(island1, island2)
465
print(f"Archipelago: {len(archipelago)} islands")
466
print(f"Description: {archipelago}")
467
468
# Convert multipolygon to cells
469
all_cells = h3.h3shape_to_cells(archipelago, resolution=8)
470
print(f"Total coverage: {len(all_cells)} cells")
471
472
# Convert individual islands for comparison
473
island1_cells = h3.h3shape_to_cells(island1, resolution=8)
474
island2_cells = h3.h3shape_to_cells(island2, resolution=8)
475
476
print(f"Island 1: {len(island1_cells)} cells")
477
print(f"Island 2: {len(island2_cells)} cells")
478
print(f"Sum matches total: {len(island1_cells) + len(island2_cells) == len(all_cells)}")
479
480
# Convert back to shape (should remain multipolygon)
481
recovered_shape = h3.cells_to_h3shape(all_cells, tight=False) # Force multipolygon
482
print(f"Recovered type: {type(recovered_shape).__name__}")
483
print(f"Recovered islands: {len(recovered_shape)}")
484
```
485
486
### Large Area Processing
487
488
```python
489
import h3
490
from h3 import LatLngPoly
491
import time
492
493
# Create a large polygon (rough outline of California)
494
california = LatLngPoly([
495
(42.0, -124.4), (32.5, -124.4), (32.5, -114.1),
496
(35.0, -114.1), (36.0, -120.0), (42.0, -120.0)
497
])
498
499
print("Processing large area (California outline):")
500
501
# Process at different resolutions
502
for resolution in range(3, 8):
503
start_time = time.time()
504
cells = h3.h3shape_to_cells(california, resolution)
505
elapsed = time.time() - start_time
506
507
print(f"Resolution {resolution}: {len(cells):,} cells in {elapsed:.2f}s")
508
509
# Estimate coverage area (very rough)
510
avg_area = h3.average_hexagon_area(resolution, 'km^2')
511
total_area = len(cells) * avg_area
512
print(f" Estimated area: {total_area:,.0f} km²")
513
514
# Note: Higher resolutions may take significant time and memory
515
```