A5 Geospatial Index icon

A5 Geospatial Index

Encode lat/lng to a pentagonal cell ID and aggregate, join, or spatially filter by that ID. A5 is a pentagon-based discrete global grid system — newer than Uber's H3 (hexagonal) and Google's S2 (square), with more uniform cell area across the globe. 31 resolution levels from continental scale down to sub-square-meter precision.

Install

-- Install the extension
INSTALL a5 FROM community;

-- Load it into your session
LOAD a5;

-- Encode a point as an A5 cell at resolution 10
SELECT a5_lonlat_to_cell(-74.0060, 40.7128, 10) AS nyc_cell;

-- Aggregate point data into pentagonal buckets
SELECT a5_lonlat_to_cell(longitude, latitude, 10) AS cell_id,
       COUNT(*)                                  AS point_count
FROM   points
GROUP  BY cell_id
ORDER  BY point_count DESC;

Technical Overview

Why Use A5?

A5 is a pentagonal discrete global grid system — an alternative to Uber's H3 (hexagonal) and Google's S2 (square). Use it to convert (lon, lat) points into integer cell IDs that you can GROUP BY, JOIN, and index, when the area-uniformity of pentagonal cells matters for your bucketing.

What this extension is for

Any spatial analysis that benefits from collapsing (lon, lat) to a fixed-cardinality integer key. The output of a5_lonlat_to_cell is a UBIGINT you can put in a GROUP BY, an index, or a join key — same shape you'd use H3 or S2 IDs in.

  • Spatial aggregation: a5_lonlat_to_cell returns a single integer per point. GROUP BY it for heatmap-style density queries — counts, sums, percentiles per cell — without touching the spatial extension.
  • Spatial joins via shared cell ID: Encode both sides of a join at the same resolution and join on the cell ID. Faster than polygon-on-polygon ST_Intersects for the common "are these points in the same neighborhood" question.
  • Hierarchical roll-up: a5_cell_to_parent walks a fine-grained cell up to a coarser resolution. The 31-level hierarchy means one encode at the finest resolution you'll need lets you re-aggregate at every coarser zoom without re-reading the source rows.
  • Bounding-region search: a5_grid_disk, a5_grid_disk_vertex, and a5_spherical_cap generate a set of cell IDs covering a neighborhood — turn "all points within k cells / X meters" into an IN (...) predicate against an indexed cell column.

A5 vs H3 vs S2

All three are global cell-based spatial indices that produce integer IDs. The shape of the cell, and how uniform its area is across the globe, is what differs.

  • Cell shape: H3 tiles the world with hexagons (with 12 unavoidable pentagons at the icosahedral vertices). S2 uses curvilinear quadrilaterals derived from a cube projection. A5 uses pentagons throughout, derived from a dodecahedral subdivision — see the A5 motivation page for the geometric argument.
  • Area uniformity: H3 hex area drifts noticeably with latitude — same-resolution cells near the poles are smaller than at the equator. A5 cells are equal-area at each resolution by construction, which matters when you're computing densities or comparing counts across very different latitudes. The A5 vs H3 page in the upstream docs walks through the trade.
  • Neighbor count: Hexagons have a clean 6-neighbor structure that's nice for grid traversal. Pentagons have 5 edge-neighbors, which is slightly less convenient — but A5 has no "pentagon exceptions" the way H3 does, so neighborhood queries don't need special-case logic.
  • Ecosystem maturity: H3 is older and has a much larger ecosystem (Postgres, BigQuery, Snowflake, Spark, plus Uber's first-party libraries). S2 is widely deployed inside Google. A5 is newer — the upstream library and specification are at github.com/felixpalmer/a5. If you need the broadest tool support today, H3 is still the safest pick. Reach for A5 specifically when uniform cell area matters.

🧭 How it works

The extension is a thin DuckDB binding around the upstream A5 Rust library. Cell IDs are 64-bit unsigned integers (UBIGINT); the resolution level is encoded in the ID itself, so a5_get_resolution recovers it without a separate column.

  • Cell IDs are UBIGINTs: Every function returns or accepts UBIGINT cell IDs. Use a5_u64_to_hex / a5_hex_to_u64 when you need to round-trip them through systems that prefer the canonical hex string form (matching the A5 reference library).
  • 31 resolution levels: Resolution 0 is the 12 base cells covering the globe (a5_get_res0_cells); resolution 30 is sub-square-meter. a5_cell_area returns the equal-area cell size for any level. Pick the coarsest resolution that still distinguishes your phenomena — every level finer is roughly a 4× row count.
  • Boundary as a polygon: a5_cell_to_boundary returns the cell's vertices as [lon, lat] pairs. Pair with the spatial extension's ST_MakePolygon / ST_AsGeoJSON to render cells on a map (the Cookbook shows the exact pattern).
  • Compaction: a5_compact replaces complete groups of sibling cells with their parent — useful for shrinking a coverage set before storing or transmitting it. a5_uncompact is the inverse, expanding to a target resolution.

🎯 Common Use Cases

Heatmap-style density aggregation

GROUP BY a5_lonlat_to_cell(lon, lat, 10) to count points per cell, then join with a5_cell_to_boundary for rendering. Because cells are equal-area, the resulting counts are directly comparable across latitudes — no per-cell area normalization needed.

Pre-indexed proximity search

Encode all points into A5 cells at ingest time. For "events near (lon, lat)", expand to a a5_spherical_cap of cell IDs and filter via WHERE cell_id IN (...) — pushed down to whatever index you have on the cell column.

Multi-zoom rollup

Store the finest resolution you'll need; use a5_cell_to_parent to roll up to coarser views in the same query. One source table powers every zoom level without re-reading raw points.

Coverage set compression

Combine a5_compact with a5_uncompact when storing or shipping a region-of-interest as a set of cells — fewer rows to ship, expandable on the consumer side.

Deep Dive

Technical Details

A5 example renderings — pentagonal cells at multiple resolutions

DuckDB ↔ A5 Pentagonal Grid

A pentagon-based discrete global grid system — an alternative to H3 and S2 with equal-area cells at every resolution.

What you can do with one query

Turn a table of (longitude, latitude) points into a heatmap-style aggregation in one statement:

SELECT
  a5_lonlat_to_cell(longitude, latitude, 10) AS cell_id,
  COUNT(*)                                   AS point_count
FROM   pickups
GROUP  BY cell_id
ORDER  BY point_count DESC;

a5_lonlat_to_cell returns a UBIGINT you can GROUP BY, JOIN, or index — the same role H3 / S2 cell IDs play in those systems. Because A5 cells are equal-area at each resolution, the resulting counts are directly comparable across latitudes without per-cell normalization.

Best fit, scoped honestly

A5 is a newer discrete global grid system. Its specification and reference library at github.com/felixpalmer/a5 are stable, but the surrounding ecosystem is much smaller than H3’s — fewer first-party bindings (Postgres, BigQuery, Spark), fewer Stack Overflow answers, fewer existing pipelines that emit A5 IDs.

Reach for A5 specifically when uniform cell area matters: cross-latitude density comparisons, equal-area sampling, anything where H3’s hex-area variation near the poles is a problem. For “the broadest tool support” or “matching what the rest of my stack uses,” H3 is still the conservative pick. The upstream A5 vs H3 page walks through the trade in detail.

A5 vs H3 vs S2 at a glance

PropertyA5 (this extension)H3S2
Cell shapePentagons throughoutHexagons (+ 12 pentagon exceptions)Curvilinear quadrilaterals
Equal areaYes, at every resolutionApproximate; varies with latitudeApproximate; varies with face
Edge neighbors56 (mostly)4
ID typeUBIGINTUBIGINTUBIGINT
Resolution levels31 (0–30)16 (0–15)31 (0–30)
DuckDB extension hereYesVia h3 community extensionNo first-party DuckDB extension as of writing
EcosystemNewer; small but growingLarge (Uber, Postgres, BigQuery, Snowflake, …)Large inside Google products

If you’re already on H3 and the area drift across latitudes isn’t biting you, there’s no reason to switch. If you’re starting fresh and equal-area cells matter, A5 is a good pick.

Resolution guide

Pick the coarsest resolution that still distinguishes the phenomena you’re measuring — each finer level is roughly a 4× row count after GROUP BY.

ResolutionCell area (approx)Typical use
0–542M km² – 33k km²Continental / country-scale rollup
6–108k km² – 130 km²Regional / metro-scale analysis
11–1532 km² – 32 hectaresCity / district analysis
16–208 hectares – 124 m²Neighborhood / building analysis
21–2531 m² – 0.5 m²Room / vehicle scale
26–308 cm² – 0.03 mm²Sub-meter precision

a5_cell_area returns the exact equal-area size for any resolution; a5_get_num_cells returns the global cell count.

Hierarchy and compaction

A5’s 31-level hierarchy means one encode at the finest resolution you’ll need supports every coarser zoom:

-- Encode once at the finest resolution
CREATE TABLE indexed AS
SELECT *, a5_lonlat_to_cell(lon, lat, 15) AS cell_15
FROM raw_points;

-- Roll up to a coarser view without re-reading raw points
SELECT a5_cell_to_parent(cell_15, 10) AS cell_10,
       COUNT(*)                       AS n
FROM indexed
GROUP BY cell_10;

a5_cell_to_parent walks any cell to a coarser ancestor; a5_cell_to_children goes the other way. For a region-of-interest expressed as a set of cells, a5_compact replaces complete groups of siblings with their parent — useful before storing or shipping a coverage set. a5_uncompact is the inverse.

Three traversal primitives, depending on how you define “near”:

  • a5_grid_disk — all cells within k edge-steps of a center cell.
  • a5_grid_disk_vertex — all cells within k vertex-steps (a slightly wider disk that includes corner-touching cells).
  • a5_spherical_cap — all cells within a metric radius (in meters) of a center cell.

Each returns a UBIGINT[]. The typical use is to expand to a coverage set, then probe an indexed cell column with WHERE cell_id IN (UNNEST(...)) — much cheaper than ST_DWithin against raw geometries.

Boundary rendering

a5_cell_to_boundary returns cell vertices as [lon, lat] pairs. Pair with the spatial extension to render cells as GeoJSON or any other format DuckDB-spatial supports — see the Cookbook for the exact polygon-construction pattern. The closed_ring variant repeats the first vertex at the end so the result is directly usable as a polygon ring; the segments overload interpolates additional points along each edge for smoother rendering on curved projections.

Cell IDs as hex strings

A5 IDs round-trip through the canonical 16-character hex form used by the reference A5 library:

SELECT a5_u64_to_hex(a5_lonlat_to_cell(-74.0060, 40.7128, 10));
-- e.g. '2607000000000000'

SELECT a5_hex_to_u64('2607000000000000');
-- the corresponding UBIGINT

Use a5_u64_to_hex / a5_hex_to_u64 when sharing cell IDs with non-DuckDB code (the upstream JS / TypeScript library expects hex strings; integer storage is more compact inside DuckDB).

Sibling extensions

For other geospatial / spatial-key work in DuckDB:

  • geosilo — geographic data utilities, complementary to A5 when you need richer spatial primitives alongside cell-based indexing.
  • lindel — space-filling curves (Hilbert, Morton). A different approach to the same “flatten 2D into a sortable 1D key” problem. Use space-filling curves when you want neighbor locality on a sorted column; use A5 when you want explicit equal-area cell membership.

Reference

Extension Contents

Quick reference to all available functions and settings organized by category.

Name Description
Cell Properties

Inspect a specific cell — its area in square meters, its resolution level, and the polygon vertices that form its boundary. Useful for filtering, validation, and rendering cells on a map.

a5_cell_area() Returns the area in square meters of an A5 cell at the specified resolution level
a5_cell_to_boundary() Returns the boundary vertices of an A5 cell as a closed ring of [lon, lat] points
a5_get_resolution() Returns the resolution level (0-30) of an A5 cell
Coordinate Conversion

Translate between geographic coordinates (longitude/latitude), spherical coordinates, and A5 cell IDs in their numeric or hex string forms. Use these as your entry and exit points for indexing data into A5.

a5_cell_to_lonlat() Returns the center point [longitude, latitude] of an A5 cell
a5_cell_to_spherical() Returns the spherical coordinates [theta, phi] in radians of an A5 cell center
a5_hex_to_u64() Converts an A5 hex string representation to a UBIGINT cell ID
a5_lonlat_to_cell() Converts a longitude/latitude coordinate to an A5 cell at the specified resolution
a5_u64_to_hex() Converts a UBIGINT A5 cell ID to its hex string representation
Hierarchy

Navigate up and down A5's 31-level resolution hierarchy. Roll cells up to a coarser parent for aggregation, expand to children for fine-grained analysis, or compact a set of sibling cells back into their parent.

a5_cell_to_children() Returns the immediate child A5 cells (one resolution finer)
a5_cell_to_parent() Returns the parent A5 cell at the specified coarser resolution
a5_compact() Compacts a list of A5 cells by merging complete sets of sibling cells into parent cells
a5_get_num_children() Returns the number of child cells at child_resolution that fit within a cell at parent_resolution
a5_uncompact() Expands a compacted list of A5 cells to the specified target resolution
Traversal

Find cells in a neighborhood: edge- or vertex-adjacent grid disks for fixed-step traversal, or all cells within a metric radius. Use these for proximity queries and spatial joins.

a5_grid_disk() Returns all A5 cells within k edge-steps of the given cell (edge adjacency)
a5_grid_disk_vertex() Returns all A5 cells within k vertex-steps of the given cell (vertex adjacency)
a5_spherical_cap() Returns all A5 cells within the specified radius (in meters) of the given cell
Utilities

General-purpose helpers — total cell count at a resolution, the 12 base cells covering the globe at resolution 0, and parent/child cell ratios.

a5_get_num_cells() Returns the total number of A5 cells at the specified resolution level (0-30)
a5_get_res0_cells() Returns all 12 resolution 0 (root) A5 cells covering the entire globe

API Reference

Function Documentation

Detailed documentation for each function including signatures, parameters, and examples.

a5_cell_area

Scalar Function Cell Properties
Signature
a5_cell_area(resolution: INTEGER) → DOUBLE
Parameters (Positional)
Parameter Type Mode Description
resolution INTEGER Positional
Returns
Description

Returns the area in square meters of an A5 cell at the specified resolution level

Examples
1
SELECT a5_cell_area(10);

Output

a5_cell_area(10)
32429099.068923894

a5_cell_to_boundary

Scalar Function Cell Properties
Signature
a5_cell_to_boundary(cell: UBIGINT) → DOUBLE[2][]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
Returns
Description

Returns the boundary vertices of an A5 cell as a closed ring of [lon, lat] points

Examples
1
SELECT a5_cell_to_boundary(a5_lonlat_to_cell(-122.4, 37.8, 5));

Output

a5_cell_to_boundary(a5_lonlat_to_cell(-122.4, 37.8, 5))
[(-123.91841441068892, 37.07072636323091), (-123.07208493364051, 36.93440251226853), (-122.23003392974138, 36.7923909949803), (-121.39776711864215, 36.97396218211518), (-120.56172281384751, 37.15058817528356), (-120.90288444799263, 37.72760911298699), (-121.2491402184125, 38.30267874469805), (-122.1294038096313, 38.23172342470604), (-123.00840835218182, 38.15508109275353), (-123.46729324681684, 37.61361779528323), (-123.91841441068892, 37.07072636323091)]

a5_cell_to_children

Scalar Function Hierarchy
Signature
a5_cell_to_children(cell: UBIGINT) → UBIGINT[]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
Returns
Description

Returns the immediate child A5 cells (one resolution finer)

Examples
1
SELECT a5_cell_to_children(a5_lonlat_to_cell(-122.4, 37.8, 5));

Output

a5_cell_to_children(a5_lonlat_to_cell(-122.4, 37.8, 5))
[1936688577257668608, 1936970052234379264, 1937251527211089920, 1937533002187800576]

a5_cell_to_lonlat

Scalar Function Coordinate Conversion
Signature
a5_cell_to_lonlat(cell: UBIGINT) → DOUBLE[2]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
Returns
Description

Returns the center point [longitude, latitude] of an A5 cell

Examples
1
SELECT a5_cell_to_lonlat(a5_lonlat_to_cell(-122.4, 37.8, 10));

Output

a5_cell_to_lonlat(a5_lonlat_to_cell(-122.4, 37.8, 10))
(-122.37432272230852, 37.78289994807168)

a5_cell_to_parent

Scalar Function Hierarchy
Signature
a5_cell_to_parent(cell: UBIGINT, parent_resolution: INTEGER) → UBIGINT
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
parent_resolution INTEGER Positional
Returns
Description

Returns the parent A5 cell at the specified coarser resolution

Examples
1
SELECT a5_cell_to_parent(a5_lonlat_to_cell(-122.4, 37.8, 10), 5);

Output

a5_cell_to_parent(a5_lonlat_to_cell(-122.4, 37.8, 10), 5)
1937110789722734592

a5_cell_to_spherical

Scalar Function Coordinate Conversion
Signature
a5_cell_to_spherical(cell: UBIGINT) → DOUBLE[2]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
Returns
Description

Returns the spherical coordinates [theta, phi] in radians of an A5 cell center

Examples
1
SELECT a5_cell_to_spherical(a5_lonlat_to_cell(-122.4, 37.8, 10));

Output

a5_cell_to_spherical(a5_lonlat_to_cell(-122.4, 37.8, 10))
(-0.5126786470476677, 0.913527819271573)

a5_compact

Scalar Function Hierarchy
Signature
a5_compact(cells: UBIGINT[]) → UBIGINT[]
Parameters (Positional)
Parameter Type Mode Description
cells UBIGINT[] Positional
Returns
Description

Compacts a list of A5 cells by merging complete sets of sibling cells into parent cells

Examples
1
SELECT a5_compact(a5_cell_to_children(a5_lonlat_to_cell(-122.4, 37.8, 5)));

Output

a5_compact(a5_cell_to_children(a5_lonlat_to_cell(-122.4, 37.8, 5)))
[1937110789722734592]

a5_get_num_cells

Scalar Function Utilities
Signature
a5_get_num_cells(resolution: INTEGER) → UBIGINT
Parameters (Positional)
Parameter Type Mode Description
resolution INTEGER Positional
Returns
Description

Returns the total number of A5 cells at the specified resolution level (0-30)

Examples
1
SELECT a5_get_num_cells(5);

Output

a5_get_num_cells(5)
15360

a5_get_num_children

Scalar Function Hierarchy
Signature
a5_get_num_children(parent_resolution: INTEGER, child_resolution: INTEGER) → UBIGINT
Parameters (Positional)
Parameter Type Mode Description
parent_resolution INTEGER Positional
child_resolution INTEGER Positional
Returns
Description

Returns the number of child cells at child_resolution that fit within a cell at parent_resolution

Examples
1
SELECT a5_get_num_children(0, 1);

Output

a5_get_num_children(0, 1)
5

a5_get_res0_cells

Scalar Function Utilities
Signature
a5_get_res0_cells() → UBIGINT[]
Parameters
Parameter Type Mode Description
Returns
Description

Returns all 12 resolution 0 (root) A5 cells covering the entire globe

Examples
1
SELECT a5_get_res0_cells();

Output

a5_get_res0_cells()
[144115188075855872, 432345564227567616, 720575940379279360, 1008806316530991104, 1297036692682702848, 1585267068834414592, 1873497444986126336, 2161727821137838080, 2449958197289549824, 2738188573441261568, 3026418949592973312, 3314649325744685056]

a5_get_resolution

Scalar Function Cell Properties
Signature
a5_get_resolution(cell: UBIGINT) → INTEGER
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
Returns
Description

Returns the resolution level (0-30) of an A5 cell

Examples
1
SELECT a5_get_resolution(a5_lonlat_to_cell(-122.4, 37.8, 10));

Output

a5_get_resolution(a5_lonlat_to_cell(-122.4, 37.8, 10))
10

a5_grid_disk

Scalar Function Traversal
Signature
a5_grid_disk(cell: UBIGINT, k: INTEGER) → UBIGINT[]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
k INTEGER Positional
Returns
Description

Returns all A5 cells within k edge-steps of the given cell (edge adjacency)

Examples
1
SELECT a5_grid_disk(a5_lonlat_to_cell(-122.4, 37.8, 10), 1);

Output

a5_grid_disk(a5_lonlat_to_cell(-122.4, 37.8, 10), 1)
[1936189948734472192, 1936191048246099968, 1937277365734342656, 1937278465245970432, 1937279564757598208, 1937281763780853760]

a5_grid_disk_vertex

Scalar Function Traversal
Signature
a5_grid_disk_vertex(cell: UBIGINT, k: INTEGER) → UBIGINT[]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
k INTEGER Positional
Returns
Description

Returns all A5 cells within k vertex-steps of the given cell (vertex adjacency)

Examples
1
SELECT a5_grid_disk_vertex(a5_lonlat_to_cell(-122.4, 37.8, 10), 1);

Output

a5_grid_disk_vertex(a5_lonlat_to_cell(-122.4, 37.8, 10), 1)
[1936188849222844416, 1936189948734472192, 1936191048246099968, 1936192147757727744, 1937277365734342656, 1937278465245970432, 1937279564757598208, 1937281763780853760]

a5_hex_to_u64

Scalar Function Coordinate Conversion
Signature
a5_hex_to_u64(hex: VARCHAR) → UBIGINT
Parameters (Positional)
Parameter Type Mode Description
hex VARCHAR Positional
Returns
Description

Converts an A5 hex string representation to a UBIGINT cell ID

Examples
1
SELECT a5_hex_to_u64('1600000000000000');

Output

a5_hex_to_u64('1600000000000000')
1585267068834414592

a5_lonlat_to_cell

Scalar Function Coordinate Conversion
Signature
a5_lonlat_to_cell(longitude: DOUBLE, latitude: DOUBLE, resolution: INTEGER) → UBIGINT
Parameters (Positional)
Parameter Type Mode Description
longitude DOUBLE Positional
latitude DOUBLE Positional
resolution INTEGER Positional
Returns
Description

Converts a longitude/latitude coordinate to an A5 cell at the specified resolution

Examples
1
SELECT a5_lonlat_to_cell(-122.4194, 37.7749, 10);

Output

a5_lonlat_to_cell(-122.4194, 37.7749, 10)
1937278465245970432

a5_spherical_cap

Scalar Function Traversal
Signature
a5_spherical_cap(cell: UBIGINT, radius: DOUBLE) → UBIGINT[]
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
radius DOUBLE Positional
Returns
Description

Returns all A5 cells within the specified radius (in meters) of the given cell

Examples
1
SELECT a5_spherical_cap(a5_lonlat_to_cell(-122.4, 37.8, 10), 1000.0);

Output

a5_spherical_cap(a5_lonlat_to_cell(-122.4, 37.8, 10), 1000.0)
[1937278465245970432]

a5_u64_to_hex

Scalar Function Coordinate Conversion
Signature
a5_u64_to_hex(cell: UBIGINT) → VARCHAR
Parameters (Positional)
Parameter Type Mode Description
cell UBIGINT Positional
Returns
Description

Converts a UBIGINT A5 cell ID to its hex string representation

Examples
1
SELECT a5_u64_to_hex(a5_lonlat_to_cell(-122.4, 37.8, 10));

Output

a5_u64_to_hex(a5_lonlat_to_cell(-122.4, 37.8, 10))
1ae2988000000000

a5_uncompact

Scalar Function Hierarchy
Signature
a5_uncompact(cells: UBIGINT[], target_resolution: INTEGER) → UBIGINT[]
Parameters (Positional)
Parameter Type Mode Description
cells UBIGINT[] Positional
target_resolution INTEGER Positional
Returns
Description

Expands a compacted list of A5 cells to the specified target resolution

Examples
1
SELECT a5_uncompact([a5_lonlat_to_cell(-122.4, 37.8, 5)], 7);

Output

a5_uncompact(main.list_value(a5_lonlat_to_cell(-122.4, 37.8, 5)), 7)
[1936583024141402112, 1936653392885579776, 1936723761629757440, 1936794130373935104, 1936864499118112768, 1936934867862290432, 1937005236606468096, 1937075605350645760, 1937145974094823424, 1937216342839001088, 1937286711583178752, 1937357080327356416, 1937427449071534080, 1937497817815711744, 1937568186559889408, 1937638555304067072]

Practical Examples

Cookbook

Real-world recipes and patterns for common use cases.

Encode a point as a cell ID

The everyday entry point — convert (lon, lat) to a UBIGINT cell ID at a chosen resolution:

SELECT a5_lonlat_to_cell(-74.0060, 40.7128, 15) AS nyc_cell;

Output

nyc_cell
2742821848331845632

Higher resolution = smaller cells. See a5_lonlat_to_cell for the full signature and a5_cell_area for the equal-area cell size at any resolution:

SELECT a5_cell_area(15) AS cell_area_m2;

Output

cell_area_m2
31669.04205949599

Aggregate point data into cells

The common heatmap pattern — bucket points by cell, then count / sum / aggregate:

SELECT a5_lonlat_to_cell(longitude, latitude, 10) AS cell_id,
       COUNT(*)                                   AS point_count,
       AVG(amount)                                AS avg_amount
FROM   transactions
GROUP  BY cell_id
ORDER  BY point_count DESC
LIMIT  100;

Because A5 cells are equal-area at each resolution, the resulting counts are directly comparable across latitudes — no per-cell area normalization needed.

Recover the cell center

Round-trip a cell ID back to (lon, lat) for labeling or display:

SELECT a5_cell_to_lonlat(a5_lonlat_to_cell(-74.0060, 40.7128, 15)) AS center_coords;

Output

center_coords
[-74.00764805615836, 40.71280225138428]

For radians instead of degrees, use a5_cell_to_spherical.

Render a cell as a polygon

a5_cell_to_boundary returns the cell’s vertices as [lon, lat] pairs — pair with the spatial extension to produce GeoJSON:

SELECT
    ST_AsGeoJSON(
        ST_MakePolygon(
            ST_MakeLine(
                list_transform(
                    a5_cell_to_boundary(
                        a5_lonlat_to_cell(-3.7037, 40.41677, 10)
                    ),
                    x -> ST_Point(x[1], x[2])
                )
            )
        )
    ) AS geojson;

The cell at resolution 10 covering Madrid (-3.7037, 40.41677):

A5 cell at resolution 10 (Madrid). The five-sided shape reflects A5's pentagonal partitioning of the globe.

Roll up to a coarser resolution

Encode once at the finest resolution you’ll need, then re-aggregate at every coarser zoom without re-reading raw points:

-- Encode at resolution 15 once
CREATE TABLE indexed AS
SELECT *, a5_lonlat_to_cell(lon, lat, 15) AS cell_15
FROM raw_points;

-- Roll up to resolution 10
SELECT a5_cell_to_parent(cell_15, 10) AS cell_10,
       COUNT(*)                       AS n
FROM indexed
GROUP BY cell_10;

a5_get_resolution recovers the resolution from a cell ID; a5_get_num_children returns the parent/child cell ratio between two resolutions.

Drill down to children

The inverse of roll-up — get the immediate children, or all descendants at a target resolution:

-- Immediate children (one level finer)
SELECT a5_cell_to_children(a5_lonlat_to_cell(-74.0060, 40.7128, 10)) AS kids;

-- All descendants at resolution 12
SELECT a5_cell_to_children(a5_lonlat_to_cell(-74.0060, 40.7128, 10), 12) AS descendants;

Neighborhood search by cell-step

a5_grid_disk returns all cells within k edge-steps of a center cell — the typical “k-ring” pattern from H3:

SELECT unnest(a5_grid_disk(a5_lonlat_to_cell(-74.0060, 40.7128, 15), 1)) AS neighbor_cell;

a5_grid_disk_vertex is the slightly wider variant that includes vertex-touching cells.

Neighborhood search by metric radius

When you have a real-world distance budget rather than a step count:

-- All cells within 5 km of Times Square at resolution 15
SELECT a5_spherical_cap(a5_lonlat_to_cell(-74.0060, 40.7128, 15), 5000.0) AS nearby_cells;

The typical proximity-query pattern: expand to a coverage set, then probe an indexed cell column.

WITH cells AS (
  SELECT unnest(a5_spherical_cap(a5_lonlat_to_cell(-74.0060, 40.7128, 15), 5000.0)) AS c
)
SELECT t.*
FROM   transactions t
JOIN   cells ON cells.c = t.cell_15;

Compact a coverage set

a5_compact replaces complete groups of sibling cells with their parent — fewer rows to ship, expandable on the consumer side:

SELECT a5_compact(a5_cell_to_children(a5_lonlat_to_cell(-122.4, 37.8, 5))) AS compacted;

Output

compacted
[1937110789722734592]

a5_uncompact is the inverse — expand a mixed-resolution set to a uniform target resolution:

SELECT a5_uncompact([a5_lonlat_to_cell(-122.4, 37.8, 5)], 7) AS expanded;

Round-trip cell IDs as hex strings

Useful when sharing cell IDs with the upstream JS / TypeScript A5 library, which uses the canonical 16-character hex form:

SELECT a5_u64_to_hex(a5_lonlat_to_cell(-122.4, 37.8, 10)) AS hex_id;
-- '1ae2988000000000'

SELECT a5_hex_to_u64('1ae2988000000000') AS cell_id;

a5_u64_to_hex and a5_hex_to_u64 are pure conversions — store the UBIGINT form for compactness, hex for interchange.

Spatial join via shared cell ID

Encode both sides at the same resolution and join on the cell ID — much cheaper than ST_Intersects for the “are these in the same neighborhood” question:

WITH events AS (
  SELECT *, a5_lonlat_to_cell(lon, lat, 12) AS cell FROM event_points
),
zones AS (
  SELECT *, a5_lonlat_to_cell(lon, lat, 12) AS cell FROM zone_centroids
)
SELECT z.zone_name, COUNT(*) AS event_count
FROM   events e
JOIN   zones  z USING (cell)
GROUP  BY z.zone_name;

For polygon zones rather than centroids, encode each zone as a covering set (via a5_spherical_cap or by walking children of a parent cell) and UNNEST it before joining.

Inspect the base grid

The 12 root cells covering the entire globe at resolution 0:

SELECT unnest(a5_get_res0_cells()) AS root_cell;

a5_get_num_cells returns the total cell count at any resolution — useful for sizing decisions before encoding a large dataset.

Platform Support

Compatibility

Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.

Quick Facts

Software License MIT
Pricing Free
Written In Rust
Source Available Yes
View on GitHub
Usage
49,115+
loads in last 30 days

Platforms

Linux
Linux (musl)
macOS
Windows
WASM
Supported platform architectures
Linux: x86_64, aarch64
Linux (musl): x86_64
macOS: Intel, Apple Silicon
Windows: x86_64
WASM: threads, mvp, eh
Compiled binary sizes
Platform Architecture Size
Linux aarch64 3.69 MB
Linux x86_64 4.09 MB
Linux (musl) x86_64 3.30 MB
macOS Apple Silicon 2.07 MB
macOS Intel 2.38 MB
Windows x86_64 7.65 MB
WASM eh 91.7 KB
WASM mvp 89.1 KB
WASM threads 89.0 KB

Gzipped download size from the DuckDB community-extensions registry.

DuckDB Versions

Release calendar
Supported
v1.4.4 v1.5.2

Spatial bucketing with pentagonal cells

Install A5 to index points by uniform-area pentagonal cells inside DuckDB — useful when H3's hex-area variation across latitudes is a problem for your aggregation.