Skip to main content

H3 Bound Cells

https://pypi.org/project/h3_bound_cells/

Convert geographic polygons into multi-resolution H3 cell coverings, with fast point-in-region lookups.

Rust core is built on h3o and exposed to Python via PyO3 / Maturin.

Overview

BoundCells is a pair of dicts keyed by H3 resolution:

  • area — cells whose interior lies inside the polygon.
    • The area layer is compacted: wherever all 7 children at a resolution are present, they collapse up to the parent.
    • A single covering naturally spans multiple resolutions (chunky cells in the interior, smaller cells near the edge).
  • border — cells that overlap the polygon boundary, materialised at every coarser resolution down to min_cell_resolution.

The border layer is what makes containment lookups cheap. To check whether an arbitrary cell falls inside the region, cell_in_bound_cells first tests the cell against the border layer at its own resolution, then walks its ancestors from fine to coarse against the compacted area cells, returning true on the first hit — no need to materialise every leaf cell of the polygon. A query cell is "inside" if it — or one of its H3 ancestors computed via cell.parent() — is an area cell.

BoundCells

Install

The package is built locally with maturin: uv run maturin develop --release

Usage

import h3
from h3_bound_cells import polygon_to_bound_cells, cell_in_bound_cells, BoundCells

# Rectangle around central London. (lat, lng) pairs; the ring need not be closed.
exterior = [
    (51.50, -0.13),
    (51.52, -0.13),
    (51.52, -0.08),
    (51.50, -0.08),
]

bc = polygon_to_bound_cells(exterior, start_res=9, min_cell_resolution=4)

print(bc)
# BoundCells(area_resolutions=[...], border_resolutions=[...])

for res, cells in bc.area.items():
    print(f"area   res {res}: {len(cells)} cells")
for res, cells in bc.border.items():
    print(f"border res {res}: {len(cells)} cells")

# Point-in-region check: Trafalgar Square at H3 resolution 11.
cell = h3.latlng_to_cell(51.5074, -0.1278, 11)
assert cell_in_bound_cells(cell, bc)

# JSON-friendly round trip
restored = BoundCells.from_dict(bc.to_dict())

API

  • polygon_to_bound_cells(exterior, holes=None, start_res=None, min_cell_resolution=None, tolerance=None) -> BoundCells
    • exterior, holes — lists of (lat, lng) tuples. Note: this is (latitude, longitude), matching the H3 convention (e.g. h3.latlng_to_cell) — the reverse of GeoJSON/shapely/WKT, which use (longitude, latitude). Swap the order when feeding in GeoJSON coordinates.
    • start_res — H3 resolution to tile at. When omitted, it is auto-picked from the polygon's planar area.
    • min_cell_resolution — floor for the border layer (default 4).
    • tolerance — Douglas–Peucker simplification distance in degrees, applied to the polygon before tiling. Tiling cost is linear in vertex count, so simplifying high-fidelity boundaries (e.g. OS/OSM data with metre-scale vertices) is a large speed-up. Defaults to 0 — no simplification, an exact covering identical to the raw geometry. Pass a small positive value (e.g. 1e-5, ≈1 m, for a ~5× speed-up on detailed boundaries) to opt in. Note that any non-zero tolerance is effectively-lossless rather than provably identical: a cell whose centroid lies within ~tolerance of the boundary can flip. Larger values are faster but shift more such edge cells.
  • cells_to_bound_cells(cells, min_cell_resolution=None) -> BoundCells
    • Build a covering directly from an existing set of H3 cells, skipping polygon tiling. cells is an iterable of H3 cells as hex strings or u64 ints.
    • The cells are the covering: every one is treated as fully inside, so the result is an exact covering with no partial boundary cells — the border layer is purely the coarser ancestor index that keeps containment lookups cheap.
    • Input must be at a single resolution (mixed resolutions raise ValueError); duplicates are de-duplicated. That resolution must be >= min_cell_resolution (default 4). An empty input yields an empty covering.
  • cell_in_bound_cells(cell: str, bound_cells: BoundCells) -> bool — membership test by H3 cell id.
  • BoundCells — frozen class:
    • .area, .border — dict[str, list[str]] keyed by stringified resolution.
    • .to_dict() / BoundCells.from_dict(d) — JSON-friendly round trip.
    • BoundCells.merge([bc1, bc2, ...]) — union of multiple results.
    • .cells_at_resolution(res) — flatten/expand the covering to a single H3 resolution (parents map up, coarser cells expand to their children).

Polars integration (optional)

Filter a Polars DataFrame/LazyFrame down to the rows whose H3 cell falls inside a covering.

Install the optional polars extra:

pip install h3_bound_cells[polars]

Importing h3_bound_cells registers a bound_cells namespace on Polars expressions (only registered when polars is installed):

import polars as pl
import h3_bound_cells  # registers the bound_cells namespace

bc = h3_bound_cells.polygon_to_bound_cells(exterior, start_res=9)

df = pl.DataFrame({"cell": [...]})  # H3 cells as hex strings or u64 ints

# Filter to rows inside the covering:
df.filter(pl.col("cell").bound_cells.is_in(bc))

# Or use the boolean result as a column:
df.with_columns(inside=pl.col("cell").bound_cells.is_in(bc))
  • pl.col(cell_column).bound_cells.is_in(bound_cells) — a boolean expression, true where the cell lies inside bound_cells.
  • Use it anywhere an expression is accepted (filter, select, with_columns, boolean combinations, …).
  • Works with both eager and lazy frames. Cells may be hex strings or u64 ints; a null cell maps to false (dropped by filter).

Predicate pushdown (Parquet)

is_in is a native plugin, so the query optimiser can't see through it to prune a scan on its own. To fix that, is_in ANDs a pure H3-index range predicate in front of the exact check (prefilter=True, on by default). Because an H3 index sorts identically as a u64 and as its 15-char hex string, Polars can push that range test into a Parquet SCAN and skip whole row groups via their min/max statistics — often a large speed-up on big scans, with an identical result. The exact plugin check still runs, so correctness is unchanged.

This is ONLY recommended in Lazy Execution as it provides purely IO-bound wins, with a collected DataFrame there is no scan to prune so this then just becomes and additional filter to run.

lf = pl.scan_parquet("atlas.parquet")  # sorted by the H3 column

# UInt64 cell column (default dtype):
lf.filter(pl.col("h3").bound_cells.is_in(bc))

# Canonical 15-char hex-string cell column:
lf.filter(pl.col("h3point").bound_cells.is_in(bc, dtype=pl.Utf8))

For the prefilter to be correct and effective:

  • Resolution — by default the range predicate covers every resolution, so it is correct whatever resolution the column is at. If you know it, pass child_res=<res> (e.g. child_res=15) for the leanest predicate; the extra ranges of the default sit in empty u64 regions for a uniform-resolution column, so they don't weaken pruning either way.
  • Matching dtype — dtype must match the column: pl.UInt64 (default) for an integer column, or pl.Utf8 for a canonical 15-char lowercase-hex string column.
  • Sorted column — row-group skipping is dramatic only when the column is sorted (tight per-group min/max). On unsorted data the prefilter still trims per-row plugin cost but skips no row groups.

Pass prefilter=False for the exact-only behaviour.

Dev visualiser

A small MapLibre app for drawing polygons and visualising the covering. It runs the Rust core entirely in the browser via WebAssembly — no backend — so it is a fully static page.

Draw a polygon (Draw polygon, click vertices, double-click to finish) and the area and border layers render colour-coded by resolution. Leave start_res blank to let the library auto-pick from the polygon's area, or set it to override. Renders are capped at 50,000 cells; reduce the polygon size or raise start_res if you trip the limit.

Running it locally

The page needs the WASM module built into dev/pkg/, and must be served over http(s) (ES-module + .wasm loading doesn't work from a file:// URL). Both are handled by:

just serve            # builds dev/pkg, then serves http://127.0.0.1:5050/

Or run the steps by hand (requires wasm-pack):

wasm-pack build --target web --out-dir dev/pkg --no-default-features --features wasm
python -m http.server -d dev 5050

The browser build compiles the same polygon_to_bound_cells core under the crate's wasm Cargo feature (PyO3 is behind the default python feature and excluded here).

Release files for h3_bound_cells 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for h3_bound_cells 0.4.1
File Size Uploaded
h3_bound_cells-0.4.1.tar.gz 5.5 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for h3_bound_cells 0.4.1
File
h3_bound_cells-0.4.1-cp39-abi3-win_arm64.whl CPython 3.9 abi3 Windows ARM64 Details
h3_bound_cells-0.4.1-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
h3_bound_cells-0.4.1-cp39-abi3-win32.whl CPython 3.9 abi3 Windows x86-32 Details
h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_x86_64.whl CPython 3.9 abi3 Linux musl 1.2+ x86-64 Details
h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_i686.whl CPython 3.9 abi3 Linux musl 1.2+ x86-32 Details
h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_aarch64.whl CPython 3.9 abi3 Linux musl 1.2+ ARM64 Details
h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details
h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.whl CPython 3.9 abi3 Linux glibc 2.12+ x86-32 Details
h3_bound_cells-0.4.1-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details

Total release size: 54.6 MB

Release files / h3_bound_cells-0.4.1.tar.gz

Download URL h3_bound_cells-0.4.1.tar.gz
Size 5.5 MB
Tags Source
SHA-256 checksum
How to use checksums
073e6c8b7a1fb726b9de29887fc9e623f3fd3560906b9306f798185b631ec82a
BLAKE2b-256 checksum
How to use checksums
0bcbf06cf3745faff7979ee419da14d5c06f607da784c00ef48fdf50615c5210
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-win_arm64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-win_arm64.whl
Size 4.4 MB
Tags CPython 3.9 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
f3c507d6b7042f82ac093723bddc281ab511c0e10e0cd2d4d799b340d4a64df8
BLAKE2b-256 checksum
How to use checksums
61c7949621e662f3c7cfe9ec36907f65d561d6695b5f3f84bc6af83e94ba606d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-win_amd64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-win_amd64.whl
Size 5.0 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
13c3ab04678bdea0fec39879be9d7f4d45235e22073f40ff1bf890479296e2fd
BLAKE2b-256 checksum
How to use checksums
9f34b475046a8b8d11a9258aacb80b3e339d307e9d9ddf537365e18374b3142d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-win32.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-win32.whl
Size 4.4 MB
Tags CPython 3.9 Windows x86-32 abi3
SHA-256 checksum
How to use checksums
8fd536cd51f679cb2288be292fcacccfcee3be38f240e8ed34de6b77bec880fc
BLAKE2b-256 checksum
How to use checksums
f2ff0d05d6f80096c654f5f381a4ad32b6c5b6c7156d48be370d8eacb3500b9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_x86_64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_x86_64.whl
Size 5.2 MB
Tags CPython 3.9 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
531d6ba31be4bc5e5861cd53c5a901992e3669789ba9d55fd8898035a07bdd89
BLAKE2b-256 checksum
How to use checksums
470f685b088e5d3780f4f9936cd2565e544e2f141cb27a10257990420d271d2f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_i686.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_i686.whl
Size 5.5 MB
Tags CPython 3.9 Linux musl 1.2+ x86-32 abi3
SHA-256 checksum
How to use checksums
fe81ae6cf74e3d60223875b93c8ca673a248141b0adc7afef5e4d3134f13dca9
BLAKE2b-256 checksum
How to use checksums
c593157f80cbb96c1e447654c186ccbee6ba2cafd29cadab847e8dc9b8dc87c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_aarch64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-musllinux_1_2_aarch64.whl
Size 4.9 MB
Tags CPython 3.9 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
b8fe3f554f43cfefcbc2dbd292f523c0ffcbe887c33a3935dd56ed70ab48c2b3
BLAKE2b-256 checksum
How to use checksums
cf7541960cc7075888dc28afed735f276a7e14cc66ad14381d014937b680d9e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 5.0 MB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
f4b3c43806620eff18fb9b02ad475db6cdf2fcfdaead8d150b40f055dd0d0f98
BLAKE2b-256 checksum
How to use checksums
3104a53572353d953485127c752ad0cadd11110ba273ff9661b6b1850ced2046
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 4.7 MB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
b651107d6504149ca97ef54ea2b14e3d574d4642642faf56d6d370eb8b140f33
BLAKE2b-256 checksum
How to use checksums
71e9ae260d92c853299f17dbb63777b770f32f6cbb6a51a58110963765c9532f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Size 5.5 MB
Tags CPython 3.9 Linux glibc 2.12+ x86-32 abi3
SHA-256 checksum
How to use checksums
45cf18cbd56010a5362fbf3394e67d76473f3ea03c493c8646b805acf5b54a7b
BLAKE2b-256 checksum
How to use checksums
1945fc6242da3e5b7ff13a267f8781a8bf90a0793c48f1e53ffc68c48baaf6a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / h3_bound_cells-0.4.1-cp39-abi3-macosx_11_0_arm64.whl

Download URL h3_bound_cells-0.4.1-cp39-abi3-macosx_11_0_arm64.whl
Size 4.5 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
f9842bcc82e989950249618f9939db8bbb08552a1d07369a363ecab84f30be29
BLAKE2b-256 checksum
How to use checksums
1d37e0965df63a1d7f5adea3819cd731e8e7697a0cd141af85f0e6b8a6322803
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.5.3

11 release files

0.5.2

11 release files

0.5.1

11 release files

0.5.0

11 release files

This release

0.4.1 This release

11 release files

0.3.1

11 release files

0.3.0

11 release files

0.2.2

11 release files

0.2.1

11 release files

0.2.0

11 release files

0.1.5

92 release files

0.1.4

92 release files

0.1.3

92 release files

0.1.2

92 release files

0.1.0

92 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page