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 tomin_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.
Install
The package is built locally with maturin:
uv sync --dev
uv run maturin develop --release
The dev dependency group (declared in pyproject.toml) also pulls in flask,
h3, polars, and pytest, used by the visualisation server, the tests, and
the optional Polars integration below.
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) -> BoundCellsexterior,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 (default4).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 to0— 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 ~toleranceof the boundary can flip. Larger values are faster but shift more such edge cells.
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 insidebound_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 byfilter).
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 emptyu64regions for a uniform-resolution column, so they don't weaken pruning either way. - Matching dtype —
dtypemust match the column:pl.UInt64(default) for an integer column, orpl.Utf8for 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 server
A small Flask + MapLibre app for drawing polygons and visualising the output:
just serve
# or:
uv run --dev dev/server.py
Then open http://127.0.0.1:5050/. Draw a polygon, hit Compute Bound Cells, and the area and border layers render colour-coded by resolution.
Renders are capped at 50,000 cells; reduce start_res or shrink the polygon if you trip the limit.
The page also accepts a pre-computed {"area": {...}, "border": {...}} blob via the Render Cells panel for offline inspection of saved output.
Release files for h3_bound_cells 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| h3_bound_cells-0.3.0.tar.gz | 5.5 MB | Details |
Built distributions (wheels)
Total release size: 54.4 MB
Release files / h3_bound_cells-0.3.0.tar.gz
| Download URL | h3_bound_cells-0.3.0.tar.gz |
|---|---|
| Size | 5.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
19d2dc12dc26d179d8354fb1e48fe4a42c98804f70200e2ce9cf1a0e67bbae4b
|
|
BLAKE2b-256 checksum How to use checksums |
101d0d96dd739bdee29b2359d6bb1c0d131cba3668ab4346cf3151f0ce04722b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-win_arm64.whl
| Download URL | h3_bound_cells-0.3.0-cp39-abi3-win_arm64.whl |
|---|---|
| Size | 4.4 MB |
| Tags | CPython 3.9 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
3a2c8353c13f54be5cec19762e7f4ff33d62549edceefa219e45f048fd9355c2
|
|
BLAKE2b-256 checksum How to use checksums |
c05abed965253ddda057ba357cc3f502d95ef108ca0dca9d2626c229084127e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-win_amd64.whl
| Download URL | h3_bound_cells-0.3.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 5.0 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
9a8004fd002f1473b853e3a2ed0c979a66fa96f9071fe0fb91922893c3aa375e
|
|
BLAKE2b-256 checksum How to use checksums |
2c4958b1895242ef2bffcfa53cffa9b441cc0d89a480fd077cebf624cfb3aad0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-win32.whl
| Download URL | h3_bound_cells-0.3.0-cp39-abi3-win32.whl |
|---|---|
| Size | 4.4 MB |
| Tags | CPython 3.9 Windows x86-32 abi3 |
|
SHA-256 checksum How to use checksums |
56809b4f156ea115140b92804054a9d6d2f45f3854db7406e954b64349d860e6
|
|
BLAKE2b-256 checksum How to use checksums |
61e6befe9382ee76d01c35345ec2746cf90d1349d1fda137f3f6212518bf3b51
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-musllinux_1_2_x86_64.whl
| Download URL | h3_bound_cells-0.3.0-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 |
c2551705216925dee89254807a9fd9d6f9a3e57fb71ac066dcdeb18fee27e0c2
|
|
BLAKE2b-256 checksum How to use checksums |
23cdf3ac6e211676e7c356ccd529484e05e7ee7cd482c18fafbc2622a1e83d2a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-musllinux_1_2_i686.whl
| Download URL | h3_bound_cells-0.3.0-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 |
e5cc1f410b265e43290dfe05fc6206f4dde4f454df0235a5cd2ba1a40b56aadb
|
|
BLAKE2b-256 checksum How to use checksums |
ecf17f3630f3263a9bfd1fb3772c478fa1828faf174f4e06fc2cfbc5a522ed28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-musllinux_1_2_aarch64.whl
| Download URL | h3_bound_cells-0.3.0-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 |
a4d9381a15b62eddd6e9e280f040019842390d83c41e2baae5a1e2064a75683a
|
|
BLAKE2b-256 checksum How to use checksums |
0821fe29a5e28e37d05beee6ab58ad15d401314463b3fcc5332731ce21f54444
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | h3_bound_cells-0.3.0-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 |
7c484b2f6edd1c1de28a98a12691c005889d9bfa79e18d135fb60344222f08c8
|
|
BLAKE2b-256 checksum How to use checksums |
00fb39c9b1c2237cb7a1117b968b8c3ee2a2ec430a2af5a9b83a57b15b3d08be
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | h3_bound_cells-0.3.0-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 |
22bab03fb6ad0d2dd7fbd8e9261f58460cf4111124cbc974f27876f40015b8da
|
|
BLAKE2b-256 checksum How to use checksums |
d9b357550d310158b66fd5d0061bb5576759dbf1faa2175b2485d4f04fb0ecce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
| Download URL | h3_bound_cells-0.3.0-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.whl |
|---|---|
| Size | 5.4 MB |
| Tags | CPython 3.9 Linux glibc 2.12+ x86-32 abi3 |
|
SHA-256 checksum How to use checksums |
f4c468e56e1e40d02670cab1e92a60a0efa10905d7c7c2d80d99a513be7431ca
|
|
BLAKE2b-256 checksum How to use checksums |
b05bd6d390468d566f18a7bcab836d5000ab57f32dd926dff3fa06661b3ce8fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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.3.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | h3_bound_cells-0.3.0-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 |
398604aaa072a6be79f838e571fd7175a9bc6b914748b5421657c6e5143fa3ce
|
|
BLAKE2b-256 checksum How to use checksums |
9b7cda1280e1875d7513c642dba02b5876a8000d76b7dfbe61c5c0fc0c1b0319
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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}
|