or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-operations.mdcell-hierarchy.mdcore-cell-operations.mddirected-edges.mdgrid-navigation.mdindex.mdmeasurements.mdpolygon-operations.md

polygon-operations.mddocs/

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

```