'A5' Discrete Global Grid System
Overview
Bindings for the "A5 geospatial index" https://a5geo.org/. 'A5' partitions the Earth's surface into pentagonal cells across 31 resolution levels using an equal-area projection onto a dodecahedron. Provides functions for indexing coordinates to cells, traversing the cell hierarchy, computing cell boundaries, and compacting/uncompacting cell sets. Powered by the 'A5' 'Rust' crate via 'extendr'.
Breaking changes
- a5R now uses the a5 Rust crate 1.0.0, which introduces A5's permanent cell-ID system. The space-filling curve and the order of the dodecahedron faces are new, so **every cell id changes**: an id written by an earlier version of a5R is still a valid cell, but in a different place, and nothing in the id says which version wrote it. Re-index stored data from its coordinates, or convert the ids once with the new
a5_migrate(). - Resolution-1 cells are now pentagons rather than triangles, so their boundaries and centres change. Cells at every other resolution keep their shape and position.
- Compacted cell sets are now
a5_coveringvectors, one covering per element. A covering stores its compacted cells followed by a compaction marker recording the resolution they stand for, as in the other A5 libraries, soa5_uncompact()no longer needs a resolution. Coverings print as their resolution, cell count and stored-cell count, work as data frame columns, and compare equal with==when they hold the same cells. Usea5_uncompact()for the cells;as_a5_cell()refuses a covering rather than treat its stored cells, whose outlines do not nest, as a grid.
Show all changes in 1.0.0Hide the rest of the changes in 1.0.0
a5_compact(),a5_grid_disk()anda5_spherical_cap()return coverings.a5_grid_disk()anda5_spherical_cap()give one covering per centre cell and lose theirsimplifyargument, which moves toa5_uncompact(). AnNAcentre gives anNAcovering.a5_polygon_to_cells()returns one covering per feature instead of the union of all features, so it can fill a column of ansfdata frame. A feature's parts and holes are still combined. A missing geometry gives anNAcovering and an empty geometry an empty covering; both used to be errors.a5_linestring_to_cells()is renameda5_line_string_to_cells(), to matchline_string_to_cells()in the other A5 libraries. The old name still works but warns.a5_set_threads()anda5_get_threads()are replaced bya5_threads(), which sets the count when given one and reports it otherwise, asoptions()andpar()do. The old functions still work but warn.a5_grid_disk()loses itsvertexargument. Use the newa5_grid_disk_vertex(), which matchesgrid_disk_vertex()in the other A5 libraries, for vertex-sharing neighbours.- Arguments take the names used by the a5 crate and a5-js:
resolutionbecomesparent_resolutionina5_cell_to_parent()andchild_resolutionina5_cell_to_children(), andclosedbecomesclosed_ringina5_cell_to_boundary()anda5_cell_to_boundary_xy(). The old names still work but warn.a5_cell_child()anda5_cell_children_range()also takechild_resolution. a5_cell_to_lonlat(),a5_cell_to_boundary(),a5_cell_to_boundary_xy()anda5_cell_to_supercell()error on an id that is not an A5 cell, as the a5 crate and a5R's other functions do. They used to returnNAor an empty boundary.a5_line_string_to_cells()gainssimplify: withsimplify = FALSEit returns ana5_cell_listwith one ordered path per feature, for a list column. The default flat form concatenates the paths, so a cell crossed by several features now appears once per feature rather than once overall; within a feature, each cell still appears once. Missing and empty geometries contribute no cells instead of an error.a5_uncompact()takes coverings, and itsresolutionargument is optional: by default each covering expands to its own resolution. It refuses to build more than.Machine$integer.maxcells.a5_is_valid()now checks the index encoding. It used to returnTRUEfor any non-missinga5_celland for any hex string that parsed, whether or not the value was a cell. Hex strings are read asa5_cell()reads them, so unpadded ids, as the a5 crate writes them, are accepted.a5_compact()'s first argument is nowcell, as in other functions that take cells, rather thancells.
New features
a5_polygon_to_cells()acceptscontainment = "center"as well as"centre", anda5_spherical_cap()gainsunits, taking the radius in any length unit or as aunitsobject.- Errors have class
a5R_errorand a subclass naming what went wrong, such asa5R_error_resolutionora5R_error_geometry, so code can handle them without matching messages. See?a5_errors. - New
a5_info()gives the a5R, bundled a5 crate, A5 index and C API versions and the thread count, as a list that prints as a summary. The same summary is shown on one line when a5R is attached. Record itsindexwith stored cells. - a5R exports a C API (
a5R.h, see?a5_c_api) for packages that convert between 64-bit ids anda5_cellvectors in compiled code, so they need not rely on howa5_cellstores its data. - New
a5_bbox()gives the longitude/latitude box of polygons as A5 sees them, with great-circle edges that can bow beyond the vertices' latitudes. - New
a5_covering_to_supercells()gives the coarse cells containing the centres of a covering's cells without building them. A covering of few fine cells takes each one's supercell directly, so converting many small coverings, such as one per feature, costs microseconds each. with(a5_threads(n), expr)runsexpronnthreads and restores the previous count afterwards, aswith(mirai::daemons(n), expr)does.a5_threads(n)returns the previous count as ana5_threadsobject to make this work.- New
a5_to_arrow_table()anda5_from_arrow_table()convert data frames with cell and covering columns to and from Arrow tables, recording the index version under the schema metadata keya5_indexand refusing tables from another index. - New
a5_migrate()converts ids from a5R before 1.0 to the current index. - New
as_a5_covering()builds coverings from cells, cell lists or raw 64-bit values written by other A5 libraries, sorting and compacting them;a5_covering_resolution()returns the resolution each covering stands for.a5_is_valid()checks covering structure. - New covering operations, computed on the compacted cells without expanding them:
a5_union(),a5_intersect()anda5_difference()combine coverings element by element, anda5_union(x)merges every covering inx;a5_contains()tests cells against coverings (a point-in-polygon test at the covering's resolution) anda5_overlaps()tests pairs of coverings;a5_count()anda5_area()give the number of cells and the area each covering stands for. Combining coverings at different resolutions is an error, as upstream. - New
a5_covering_stored()returns the compacted cells a covering stores, without the compaction marker, for index range queries witha5_cell_children_range(). - New
a5_covering_to_arrow()anda5_covering_from_arrow()convert coverings to and from Arrowlist<uint64>with the compaction marker, the layout DuckDB, Python and JavaScript A5 code read. - New
a5_is_valid_cell()checks that values are A5 cells, mirroring the upstreamis_valid_cell.a5_is_valid()becomes a generic that also accepts ana5_cell_list, returning one value per element. - New
a5_cell_to_boundary_xy()returns the boundary vertices of many cells as flatxandyvectors with per-celllengths, ready for a singlegrid::polygonGrob(). It skips building a geometry per cell, so it is about 1.5 times faster thana5_cell_to_boundary()followed bywk::wk_coords()on one thread, 3 times faster on four, and allocates an eighth of the memory.
Other changes
- As in the other A5 libraries,
a5_line_string_to_cells()gives the cell containing a single waypoint, anda5_polygon_to_cells()gives an empty covering for an outer ring of fewer than 3 distinct vertices and ignores such holes. Both used to error. a5_cell_to_parent()acceptsresolution = -1, giving the world cell.- The a5 crate 1.0.0 fixes two wrong results.
a5_polygon_to_cells()withcontainment = "centre"no longer returns a whole quintant of cells for thin sliver polygons, anda5_lonlat_to_cell()no longer returns a cell on the wrong dodecahedron face when the previous point fell near the centre of another face. - Functions returning many cells write them straight into R's vectors, in parallel when threads are enabled, instead of staging them first:
a5_uncompact()is about 2.5 times faster on millions of cells, anda5_cell_to_lonlat()anda5_cell_to_parent()up to a third faster, with lower peak memory. a5_lonlat_to_cell()returnsNAforNaNand infinite coordinates, which used to give valid cells, and errors on latitudes outside \[-90, 90\], which used to wrap onto plausible but wrong cells.a5_grid_disk(),a5_grid_disk_vertex()anda5_spherical_cap()return the world cell's covering for the world cell, which is its own disk and cap. They used to fail with a Rust panic.- Forked workers (
parallel::mclapply(),future::multicore) no longer hang aftera5_threads()sets more than one thread: they inherited the thread pool but not its threads. a5R now runs on one thread in a forked worker until the worker sets its own count. - A panic inside the a5 crate during a multi-threaded call no longer leaves every later parallel call, and
a5_threads(), failing until R restarts.a5_threads()rejects counts below 1 andNA, and keeps the previous count if the thread pool cannot be built. a5_cell()accepts ids of fewer than 16 hex digits, as written by a5-js and DuckDB, and a0xprefix. A string that is not a hex id is now an error instead of a silentNA.NAcells are missing to vctrs:==againstNAgivesNA, and sorting putsNAlast instead of among the resolution-30 cells.as_a5_covering()treatsNULLlist elements as missing coverings, so null coverings read back from Arrow stayNAinstead of becoming empty.a5_polygon_to_cells()anda5_line_string_to_cells()error onsfandterrainput in a projected CRS, and on coordinates outside the longitude/latitude range. A ring that crosses itself, such as a bow tie, is an error: the a5 crate filled it inconsistently, flooding whole quintants and running out of memory at fine resolutions.a5_grid_disk()rejects a negative orNAk, anda5_spherical_cap()a negative or non-finiteradius.a5_uncompact()'s size guard counts the 5 children of a resolution-0 cell, so it no longer lets through requests up to 25% over the limit.- Scalar resolution arguments reject
NAwith a clear error, anda5_get_num_children()errors when the child resolution is coarser than the parent instead of returning 0. a5_cell_from_arrow()anda5_covering_from_arrow()give a clear error for input that is not an Arrow array.a5_cell_children_range()anda5_cell_child()reach resolution 29 from any cell, as documented, instead of erroring beyond 20 levels.- The world cell's boundary is
POLYGON EMPTYinstead of an invalid polygon. a5_cell_to_parent()errors whenresolutionis finer than a cell's resolution, instead of returning a descendant.a5_cell_to_boundary()builds its output faster, with identical results: on a million cells, WKB takes about 25% less time on one thread and half the time on four, and WKT about 20% less.a5_cell_distance()from one cell to many (or many to one) finds the single cell's centroid once instead of once per pair, with identical results: on a million cells it is about twice as fast for"haversine"and"rhumb"and a quarter faster for"geodesic".a5_cell_to_boundary()rejects asegmentsvalue below 1 orNA; a negative value used to hang. Its documentation now gives the default,max(1, 2^(6 - resolution))segments per edge, and how far a single segment strays from the curved cell edge at each resolution.a5_polygon_to_cells()handleswk::rct()boxes that previously gave wrong results. A box covering the whole globe gives every cell instead of none, and a box reaching from pole to pole keeps its west and east edges on their meridians. Any other box spanning 180 degrees of longitude or more is an error, because its great-circle edges would run the short way round. The documentation now states that polygon edges are great circles.- New
vignette("coverings")on building, combining, testing and measuring coverings.vignette("arrow-parquet")covers storing coverings and migrating data written before 1.0, andvignette("spatial-hierarchy")is rewritten for supercells and subcells. a5_cell_to_arrow(),a5_cell_from_arrow(),a5_covering_to_arrow()anda5_covering_from_arrow()run in Rust and pass one contiguousuint64buffer through the Arrow C data interface, instead of building an R object per cell. On 10 million cells, writing to Arrow is about 90 times faster and reading about 40 times faster.a5_covering_from_arrow()now refuses a covering that holds a null cell; it used to drop the null silently.a5_cell_child()anda5_cell_children_range()follow the new order ofa5_cell_to_children()(children are now in ascending id order).a5_get_num_cells()is correct at high resolutions (a5 0.10.1).a5_polygon_to_cells()merges a feature's parts with the upstream covering union, without expanding each part to full resolution first.- New
a5_cell_to_supercell()anda5_cell_to_subcells()move between resolutions by cell centre rather than by index, wrapping the upstreamcell_to_supercell()andcell_to_subcells(). The A5 index hierarchy is not spatially nested: for about half of cells one resolution apart, and about 35% at four or more apart, the index parent is not the coarse cell containing the fine cell's centre. Supercells and subcells form an exact partition, so aggregating fine cells to a coarser grid counts each fine cell once, inside the coarse cell it lies in. Subcells are returned as one covering per cell.a5_cell_to_parent()anda5_cell_to_children()are unchanged and now document the difference. - Missing values filled in by vctrs are now
NA, not the world cell (#27). Combining withNA, subsetting with anNAindex, assigningNA,vctrs::vec_init(), and unmatched rows frommerge(all = TRUE),vctrs::vec_rbind()or dplyr joins used to produce id 0, a valid cell. The top byte of each id is now stored XOR0xFC, so a filled-in gap decodes to the NA sentinel, at no measurable cost. This changes the stored byte layout:a5_cellobjects saved withsaveRDS(),save()or qs by earlier versions do not read correctly. Re-create them from hex strings or Arrow, which store the true id. - Resolution-30 cells whose id starts with
0xFC(about 0.3% of them, in quintants 31, 39 and 41) are no longer treated asNA. The NA check now compares the full id rather than its top byte. - The benchmark suite now mirrors the TypeScript, Python and Rust A5 ports (same case names, same deterministic inputs) and runs in CI on every pull request, failing on regressions above 15%. The old cross-language comparison scripts are gone.
a5_cell_to_children(),a5_grid_disk()anda5_spherical_cap()are vectorised over their cell argument and gain asimplifyargument. The defaultsimplify = TRUEreturns one flata5_cellvector, so single-cell calls are unchanged;simplify = FALSEreturns avctrs::list_of()with one element per input, for list columns. AnNAcell now contributes no cells instead of raising an error.- New
a5_cell_listclass for those lists, with anunlist()method that concatenates the elements into onea5_cellvector. Baseunlist()on a plain list ofa5_cellvectors returns a meaningless raw vector;as_a5_cell_list()wraps such a list sounlist()works. - Base R compatibility for
a5_cell:match()and%in%are about ten times faster via an exactmtfrm()method instead of hex conversion;x[i] <- valuepast the end andlength(x) <- ngrow the vector withNA, sorbind()on data frames witha5_cellcolumns works.split()andtapply()with ana5_cellgrouping vector cannot be intercepted and give wr
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
- OK13 OK2026-09-19
- ERROR12 OK, 1 ERROR2026-09-17
- OK13 OK2026-09-08
- ERROR12 OK, 1 ERROR2026-09-07
- OK13 OK2026-08-05
Show 8 earlier changesHide 8 earlier changes
- NOTE12 OK, 1 NOTE2026-08-01
- OK13 OK2026-07-30
- NOTE12 OK, 1 NOTE2026-07-29
- OK13 OK2026-05-02
- NOTE11 OK, 1 NOTE2026-04-25
- OK14 OK2026-04-16
- NOTE13 OK, 1 NOTE2026-03-28
- OK5 OK2026-03-17
Documentation
- Help pages
- 39 (4 internal)includes a package overview page
- Examples that run
- 100%34 run in a plain check (3 of them under a condition)
- Exported functions with examples
- 94%
- Documented parameters
- 92%
- Return-value docs
- 100%
- Help pages citing references
- 0%
Downloads
Repository
- PRs merged (all time)
- 100%
- Median days to close, 50 most recently updated closed issues
- 2
- Median days to close, 50 most recently updated closed PRs
- 0
Stars over time
Forks over time
Releases over time
Issues over time
PRs over time
Commits over time
Commit identities over time
9 development-tooling and community-health practices detected across 4 families in the upstream repository
Checks run against github.com/belian-earth/a5r on 2026-10-18.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
GitHub Pages site last deployed 2026-10-16 · The repository has a site deploy workflow
pkgdown site last built 2026-10-13, before release 1.0.0 (2026-10-19)
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-02-16Named in .Rbuildignore: .claudeNamed in .gitignore: .claudeCommits: 2 by its own account · at least 17 crediting it in the message
- Claude Fable 5.115 commits · Sep 2026
- Claude Opus 4.62 commits · Mar 2026
Dependencies
Installing a5R adds 8 packages (7 with compiled code, 5.7 MB of source tarballs). Heaviest: Rcpp 1.8 MB, vctrs 1.0 MB, wk 1.0 MB.
Code & Tests
People & History
Pick two releases to compare their code metrics.
- 1.0.0Latest2026-10-19 · current release · diff ↗ ·
added 29 exports
a5_area, a5_bbox, a5_cell_child, a5_cell_children_range, a5_cell_to_boundary_xy, a5_cell_to_subcells, a5_cell_to_supercell, a5_contains, a5_count, a5_covering_from_arrow, a5_covering_resolution, a5_covering_stored, a5_covering_to_arrow, a5_covering_to_supercells, a5_difference, a5_from_arrow_table, a5_grid_disk_vertex, a5_info, a5_intersect, a5_is_valid_cell, a5_line_string_to_cells, a5_migrate, a5_overlaps, a5_threads, a5_to_arrow_table, a5_union, as_a5_cell_list, as_a5_covering, is_a5_covering - 0.6.0
- 0.5.0
- RR 4.6.0 released · 2026-04-24
- 0.4.0
- 0.3.12026-03-26 · diff ↗ ·
removed 1 export
a5_is_celladded 5 exports
a5_cell_from_arrow, a5_cell_to_arrow, a5_hex_to_u64, a5_is_valid, a5_u64_to_hex - 0.2.02026-03-16
- RR 4.5.0 released · 2025-04-11
Package metadata
- First published
- 2026-03-16
- Total releases
- 6 / 1 yrs
- License
- Apache License (>= 2)
- Minimum R
- ≥ 4.2
- Download size
- 2.0 MB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("a5R")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-21, which the citation names so these numbers can be found later. More on citing and the projects behind them.