Skip to content

Query-farm/a5

Repository files navigation

A5 Geospatial Extension for DuckDB

A high-performance DuckDB extension that provides functions for the A5 global geospatial index - a millimeter-accurate, equal-area indexing system for geospatial data.

✨ What is A5?

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

🎯 Use Cases

Spatial Data Aggregation

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;

πŸš€ Quick Start

Installation

The a5 extension is available as a DuckDB Community Extension:

INSTALL a5 FROM community;
LOAD a5;

Basic Usage

-- 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] β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code Example: Generate GeoJSON for Cell

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 g

This 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]
        ]
    ]
}
Loading

πŸ“š API Reference

Core Functions

a5_lonlat_to_cell(latitude, longitude, resolution) -> UBIGINT

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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

a5_cell_area(resolution) -> DOUBLE

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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

a5_get_resolution(cell_id) -> INTEGER

Returns the resolution level of an A5 cell.

Example:

SELECT a5_get_resolution(207618739568) as resolution;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ resolution β”‚
β”‚   int32    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     27     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Spatial Relationships

a5_cell_to_parent(cell_id, target_resolution) -> UBIGINT

Returns the parent cell at a coarser resolution level.

Example:

SELECT a5_cell_to_parent(207618739568, 10) as parent_cell;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   parent_cell    β”‚
β”‚      uint64      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   549755813888   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

a5_cell_to_children(cell_id, target_resolution) -> UBIGINT[]

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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Geometric Properties

a5_cell_to_lonlat(cell_id) -> DOUBLE[2]

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] β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

a5_cell_to_boundary(cell_id) -> DOUBLE[2][]

Returns the boundary vertices of a cell as an array of [latitude, longitude] pairs.

Example:

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] β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Utility Functions

a5_get_num_cells(resolution) -> UBIGINT

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   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

a5_get_res0_cells() -> UBIGINT[]

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 Guide

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

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Credits

πŸ”— Related Links