a5R (development version)
Breaking changes
- a5R now uses the a5 Rust crate 1.0.0-beta.3, 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. -
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()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 must now also be 16 digits. -
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. -
with(a5_set_threads(n), expr)runsexpronnthreads and restores the previous count afterwards, aswith(mirai::daemons(n), expr)does.a5_set_threads()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, tagging the table with the index version and 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
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.A panic inside the a5 crate during a multi-threaded call no longer leaves every later parallel call, and
a5_set_threads(), failing until R restarts.a5_set_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_linestring_to_cells()error onsfandterrainput in a projected CRS, and on coordinates outside the longitude/latitude range. The documentation now warns that self-intersecting rings give undefined results.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_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_subcell()move between resolutions by cell centre rather than by index, wrapping the upstreamcell_to_supercell()andcell_to_subcell(). 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 wrong results; usevctrs::vec_split()orvctrs::vec_group_id(), as documented invignette("internal-cell-representation").New
a5_cell_child()returns the i-th descendant of each cell at a finer resolution without enumerating the others, for sampling from large cells.New
a5_cell_children_range()returns the smallest and largest descendant of each cell at a finer resolution. Descendants occupy a contiguous id range among cells of that resolution, so the pair is an exactBETWEENfilter on an id-sorted store. Resolution 30 is refused because the guarantee does not hold there.Reduced per-call overhead across the package. Rust output is wrapped without re-validation, cell fields are passed to Rust without building a data frame and read there without R-level
$calls, scalar arguments are checked with a lightweight helper, defaultformat,containmentandmethodarguments skiprlang::arg_match(),a5_lonlat_to_cell()casts and recycles common inputs without vctrs,a5_cell_to_lonlat()uses low-levelwkand data frame constructors, anda5_cell_area(),a5_cell_edge_length_avg()anda5_cell_distance()cache the parsed base unit. Scalar calls are three to twenty times faster; results, recycling rules and error messages are unchanged.
a5R 0.6.0
CRAN release: 2026-09-08
- Updated the bundled ‘A5’ Rust crate to 0.10.0. The lattice curve is now laid out with an L-system and the equal-area projection is more efficient; upstream reports large speed-ups for
a5_polygon_to_cells(),a5_grid_disk(),a5_spherical_cap()anda5_cell_to_lonlat(). Cell identifiers are unchanged: all existing results and snapshots are bit-for-bit identical. -
a5_polygon_to_cells()gains acontainmentargument. The default,"centre", keeps the existing centre-in-polygon behaviour. The new"overlapping"mode additionally returns every cell that touches the polygon boundary, giving gap-free coverage; the result is a superset of the centre set. Hole interiors are still excluded in both modes. This restores the boundary-intersection use case previously served by the removeda5_grid(). - New
a5_cell_edge_length_avg()returns the average edge length of a cell at a given resolution, as aunitsvector (metres by default). Individual edges vary from the average by roughly +/-10%. - Fixed the build on Windows ARM64 by bumping
extendr-apito 0.8.2 and passing the correct--targeton Windows (#22, @jeroen).
a5R 0.5.0
CRAN release: 2026-07-02
- Removed
a5_grid(), deprecated since 0.4.0. Usea5_polygon_to_cells()instead. Note the semantics differ:a5_grid()selected every cell a geometry touched (boundary intersection), whereasa5_polygon_to_cells()selects cells whose centre lies inside the geometry. For a bounding box, pass a closed polygon, e.g.a5_polygon_to_cells(wk::rct(xmin, ymin, xmax, ymax), res). - Dropped the
lifecycleR dependency and thewktRust dependency, both of which were only needed bya5_grid(). - Replaced the heavy
geoRust dependency with the lightweightgeographiclib-rscrate fora5_cell_distance(). The haversine and rhumb methods are now computed directly; the geodesic method continues to use Karney’s algorithm. Distance results are unchanged. This removes 33 transitive crates and shrinks the vendored sources substantially. - Updated the bundled ‘A5’ Rust crate to 0.9.0, bringing a faster polyhedral projection and an
EqualAreaProjectionrefactor. -
a5_polygon_to_cells()now delegates hole handling to the upstream crate, which excludes hole interiors natively rather than subtracting hole-ring cells in R. Results are unchanged; the implementation is simpler and avoids an uncompact/recompact round-trip for single-part polygons.
a5R 0.4.0
CRAN release: 2026-05-14
-
a5_grid()is soft-deprecated in favour ofa5_polygon_to_cells(). Calling it now emits alifecycle::deprecate_warn()with guidance: usea5_polygon_to_cells()for geometry inputs (centre-in-polygon containment), or pass awk::rct()bounding box for the bbox use case. Note that the two functions are not semantically identical:a5_grid()uses boundary intersection (any cell touched by the geometry), whereasa5_polygon_to_cells()uses centre-point containment (cells whose centroid lies inside). - New
a5_polygon_to_cells()returns the A5 cells whose centres lie inside a polygon. Distinct froma5_grid(), which uses boundary-intersection semantics. Accepts wk-handleable geometries (includingMULTIPOLYGONandsfcof several polygons), terraSpatVectorobjects, numeric matrices, ordata.frame(lon, lat). Multi-part inputs are handled natively: per polygon part the outer ring’s cells are computed and any hole-ring cells are subtracted, then the results are unioned across parts and recompacted. - New
a5_linestring_to_cells()returns the A5 cells whose pentagons are intersected by a great-circle polyline, in discovery order along the path. Accepts the same input shapes asa5_polygon_to_cells(), includingMULTILINESTRING,sfcs of multiple linestrings, and terraSpatVectorobjects; per-feature outputs are concatenated with first-seen deduplication. - Bumped the embedded
a5Rust crate from 0.7.0 to 0.8.0. Transparent improvements inherited from upstream: resolution-30 (de)serialisation (a5 0.7.1), neighbour functions at resolutions 0 and 1 (a5 0.7.2), longitude normalisation incell_to_lonlat(a5 0.7.3), fastercell_to_parent, and a polar-region spiral fix ingrid_diskandspherical_cap. -
Breaking:
a5_cell_to_lonlat()replaces itsnormaliseargument withas_dataframe(defaultFALSE). WhenFALSE, centroids are returned as awk::xy()vector with WGS 84 CRS; whenTRUE, as a basedata.framewithlon/latcolumns. The previousnormaliseargument toggled longitude normalisation, but upstream a5 (>= 0.7.3) always returns normalised longitudes, so the flag’s effective job collapsed to “what container?”.as_dataframemakes that explicit. Defaults are unchanged for users who never setnormalise(still returnswk::xy); explicitnormalise = TRUE/FALSEcalls now error and must be updated. - New
lifecycledependency added toImports.
a5R 0.3.1
CRAN release: 2026-03-26
-
a5_cell()now requires hex strings to be exactly 16 characters, preventing silent construction of wrong cells from truncated input. - Improved vignettes covering the new
a5_cellrepresentation and Arrow integration.
a5R 0.3.0
-
a5_cellinternal representation now uses 8 parallel raw byte vectors instead of hex strings, reducing memory from ~80 bytes/cell to 8 bytes/cell (#12). -
a5_cellgainsvec_proxy_compare()andvec_proxy_order()methods, enablingsort(),order(),unique(),duplicated(),match(), and%in%(#12). -
a5_cellgains anis.na()method (#12). - New
a5_cell_from_arrow()anda5_cell_to_arrow()for lossless conversion betweena5_celland Arrowuint64arrays, enabling zero-copy Parquet I/O (#12). - New
a5_u64_to_hex()anda5_hex_to_u64()for explicit conversion betweena5_cellvectors and hex strings. -
a5_is_cell()has been renamed toa5_is_valid()and now accepts botha5_cellvectors and character hex strings. -
a5_cell_distance()anda5_cell_area()gain aunits = NULLoption to return plain numeric vectors withoutunitsclass overhead. - New vignettes:
vignette("internal-cell-representation")andvignette("arrow-parquet").
