A high-performance DuckDB extension that provides functions for the A5 global geospatial index - a millimeter-accurate, equal-area indexing system for geospatial data.
A5 is an innovative geospatial index that partitions the world into pentagonal cells based on a geodesic grid. Key features include:
- 🌍 Global Coverage: Seamless indexing from global to millimeter scales
- 📐 Equal Area: All cells at the same resolution level have identical area (OGC compliant)
- 🔍 31 Resolution Levels: From world-spanning cells to sub-30mm² precision
- ⚡ Fast Spatial Operations: Optimized for aggregation, filtering, and spatial joins
Group point data spatially to understand distributions:
-- Analyze restaurant density by A5 cells
SELECT a5_lonlat_to_cell(longitude, latitude, 15) as cell_id, COUNT(*) as restaurant_count
FROM restaurants
GROUP BY cell_id
ORDER BY restaurant_count DESC;The a5 extension is available as a DuckDB Community Extension:
INSTALL a5 FROM community;
LOAD a5;-- Get the A5 cell for a specific location (latitude, longitude, resolution)
SELECT a5_lonlat_to_cell(-74.0060, 40.7128, 15) as nyc_cell; -- Times Square
┌─────────────────────┐
│ nyc_cell │
│ uint64 │
├─────────────────────┤
│ 2742821848331845632 │
└─────────────────────┘
-- Find the area of that cell in square meters
SELECT a5_cell_area(15) as cell_area_m2;
┌───────────────────┐
│ cell_area_m2 │
│ double │
├───────────────────┤
│ 31669.04205949599 │
└───────────────────┘
-- Get the center coordinates of a cell
SELECT a5_cell_to_lonlat(a5_lonlat_to_cell(-74.0060, 40.7128, 15)) as center_coords;
┌─────────────────────────────────────────┐
│ center_coords │
│ double[2] │
├─────────────────────────────────────────┤
│ [-74.00764805615836, 40.71280225138428] │
└─────────────────────────────────────────┘
-- Find parent cell at lower resolution
SELECT a5_cell_to_parent(a5_lonlat_to_cell(-74.0060, 40.7128, 15), 10) as parent_cell;
┌─────────────────────┐
│ parent_cell │
│ uint64 │
├─────────────────────┤
│ 2742821365684895744 │
└─────────────────────┘
-- Get all children cells at higher resolution
SELECT a5_cell_to_children(a5_lonlat_to_cell(-74.0060, 40.7128, 10), 11) as child_cells;
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ child_cells │
│ uint64[] │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ [2742820953368035328, 2742821228245942272, 2742821503123849216, 2742821778001756160] │
└──────────────────────────────────────────────────────────────────────────────────────┘To generate a GeoJSON polygon for the A5 cell above use this SQL along with DuckDB's spatial extension:
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 gThis produces:
{
"type":"Polygon",
"coordinates":[
[
[-3.639321611065313,40.44502900567739],
[-3.6973300524360155,40.44427170464865],
[-3.7459288918337563,40.424159040292615],
[-3.70791029038422,40.394201800420205],
[-3.654438659632305,40.4080830654645],
[-3.639321611065313,40.44502900567739]
]
]
}
Visualizing that A5 cell shows:
{
"type":"Polygon",
"coordinates":[
[
[-3.639321611065313,40.44502900567739],
[-3.6973300524360155,40.44427170464865],
[-3.7459288918337563,40.424159040292615],
[-3.70791029038422,40.394201800420205],
[-3.654438659632305,40.4080830654645],
[-3.639321611065313,40.44502900567739]
]
]
}
Returns the A5 cell ID for given coordinates and resolution level.
Parameters:
latitude(DOUBLE): Latitude in decimal degrees (-90 to 90)longitude(DOUBLE): Longitude in decimal degrees (-180 to 180)resolution(INTEGER): Resolution level (0-30, where 0 is coarsest)
Example:
SELECT a5_lonlat_to_cell(-0.1278, 51.5074, 12) as london_cell;
┌─────────────────────┐
│ london_cell │
│ uint64 │
├─────────────────────┤
│ 7161033366718906368 │
└─────────────────────┘Returns the area of an A5 cell in the specified resolution in square meters.
Example:
SELECT a5_cell_area(5) as area_m2;
┌────────────────────┐
│ area_m2 │
│ double │
├────────────────────┤
│ 33207397446.578068 │
└────────────────────┘Returns the resolution level of an A5 cell.
Example:
SELECT a5_get_resolution(207618739568) as resolution;
┌────────────┐
│ resolution │
│ int32 │
├────────────┤
│ 27 │
└────────────┘Returns the parent cell at a coarser resolution level.
Example:
SELECT a5_cell_to_parent(207618739568, 10) as parent_cell;
┌──────────────────┐
│ parent_cell │
│ uint64 │
├──────────────────┤
│ 549755813888 │
└──────────────────┘Returns all children cells at a finer resolution level.
Example:
SELECT unnest(a5_cell_to_children(207618739568, 28)) as child_cells;
┌──────────────┐
│ child_cells │
│ uint64 │
├──────────────┤
│ 207618739528 │
│ 207618739544 │
│ 207618739560 │
│ 207618739576 │
└──────────────┘Returns the center coordinates [longitude, latitude] of a cell.
Example:
SELECT a5_cell_to_lonlat(207618739568) as center;
┌─────────────────────────────────────────┐
│ center │
│ double[2] │
├─────────────────────────────────────────┤
│ [-129.0078555564143, 52.76769886727584] │
└─────────────────────────────────────────┘Returns the boundary vertices of a cell as an array of [latitude, longitude] pairs.
Parameters:
cell_id(UBIGINT): The A5 cellclosed_ring(BOOLEAN): Whether to close the ring by repeating the first point at the end. Defaults to true.segments(INTEGER): Number of segments to use for each edge. If this argument is not supplied or a value is supplied that is <= 0, a resolution-appropriate value will be used.
Examples:
SELECT unnest(a5_cell_to_boundary(207618739568)) as boundary_points;
┌───────────────────────────────────────────┐
│ boundary_points │
│ double[2] │
├───────────────────────────────────────────┤
│ [-129.00785542696357, 52.767699205314614] │
│ [-129.00785579342767, 52.767698942751544] │
│ [-129.0078559316034, 52.76769861890205] │
│ [-129.00785542684645, 52.76769862844177] │
│ [-129.0078552032305, 52.767698940969176] │
│ [-129.00785542696357, 52.767699205314614] │
└───────────────────────────────────────────┘SELECT unnest(a5_cell_to_boundary(207618739568, false, 5)) as boundary_points;
┌───────────────────────────────────────────┐
│ boundary_points │
│ double[2] │
├───────────────────────────────────────────┤
│ [-129.00785550025637, 52.767699152801974] │
│ [-129.0078555735492, 52.76769910028939] │
│ [-129.00785564684202, 52.76769904777675] │
│ [-129.00785572013484, 52.767698995264155] │
│ [-129.00785579342767, 52.767698942751544] │
│ [-129.0078558210628, 52.76769887798164] │
│ [-129.00785584869794, 52.767698813211744] │
│ [-129.00785587633308, 52.767698748441845] │
│ [-129.00785590396822, 52.76769868367194] │
│ [-129.0078559316034, 52.76769861890205] │
│ [-129.007855830652, 52.76769862081001] │
│ [-129.00785572970062, 52.76769862271794] │
│ [-129.0078556287492, 52.76769862462589] │
│ [-129.00785552779783, 52.76769862653383] │
│ [-129.00785542684645, 52.76769862844177] │
│ [-129.00785538212327, 52.767698690947256] │
│ [-129.00785533740006, 52.76769875345273] │
│ [-129.00785529267688, 52.7676988159582] │
│ [-129.00785524795367, 52.7676988784637] │
│ [-129.0078552032305, 52.767698940969176] │
│ [-129.00785524797712, 52.767698993838245] │
│ [-129.00785529272372, 52.76769904670734] │
│ [-129.00785533747032, 52.76769909957643] │
│ [-129.00785538221695, 52.767699152445516] │
│ [-129.00785542696357, 52.767699205314614] │
├───────────────────────────────────────────┤
│ 25 rows │
└───────────────────────────────────────────┘Returns the total number of A5 cells at a given resolution level.
Example:
SELECT a5_get_num_cells(15) as total_cells;
┌─────────────────┐
│ total_cells │
│ uint64 │
├─────────────────┤
│ 16106127360 │
└─────────────────┘Returns all 12 base cells at resolution level 0.
Example:
SELECT unnest(a5_get_res0_cells()) as base_cells;
┌─────────────────────┐
│ base_cells │
│ uint64 │
├─────────────────────┤
│ 144115188075855872 │
│ 432345564227567616 │
│ 720575940379279360 │
│ 1008806316530991104 │
│ 1297036692682702848 │
│ 1585267068834414592 │
│ 1873497444986126336 │
│ 2161727821137838080 │
│ 2449958197289549824 │
│ 2738188573441261568 │
│ 3026418949592973312 │
│ 3314649325744685056 │
├─────────────────────┤
│ 12 rows │
└─────────────────────┘| Resolution | Cell Area (approx) | Use Case |
|---|---|---|
| 0-5 | 42M km² - 33k km² | Continental/Country analysis |
| 6-10 | 8k km² - 130 km² | Regional/State analysis |
| 11-15 | 32 km² - 32 hectares | City/District analysis |
| 16-20 | 8 hectares - 124 m² | Neighborhood/Building analysis |
| 21-25 | 31 m² - 0.5 m² | Room/Vehicle analysis |
| 26-30 | 8 cm² - 0.03 mm² | Precision measurements |
This project is licensed under the MIT License - see the LICENSE file for details.
- A5 Algorithm: This extension utilizes the
a5Rust crate created by felixpalmer - A5 Specification: Based on the A5 geospatial index specification
- DuckDB Community: Built on the excellent DuckDB database system