a5_migrate().a5_covering vectors, 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, so a5_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. Use
a5_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() and
a5_spherical_cap() return coverings.
a5_grid_disk() and a5_spherical_cap() give one
covering per centre cell and lose their simplify argument,
which moves to a5_uncompact(). An NA centre
gives an NA covering.a5_polygon_to_cells() returns one covering per feature
instead of the union of all features, so it can fill a column of an
sf data frame. A feature’s parts and holes are still
combined. A missing geometry gives an NA covering and an
empty geometry an empty covering; both used to be errors.a5_linestring_to_cells() is renamed
a5_line_string_to_cells(), to match
line_string_to_cells() in the other A5 libraries. The old
name still works but warns.a5_set_threads() and a5_get_threads() are
replaced by a5_threads(), which sets the count when given
one and reports it otherwise, as options() and
par() do. The old functions still work but warn.a5_grid_disk() loses its vertex argument.
Use the new a5_grid_disk_vertex(), which matches
grid_disk_vertex() in the other A5 libraries, for
vertex-sharing neighbours.resolution becomes parent_resolution in
a5_cell_to_parent() and child_resolution in
a5_cell_to_children(), and closed becomes
closed_ring in a5_cell_to_boundary() and
a5_cell_to_boundary_xy(). The old names still work but
warn. a5_cell_child() and
a5_cell_children_range() also take
child_resolution.a5_cell_to_lonlat(),
a5_cell_to_boundary(),
a5_cell_to_boundary_xy() and
a5_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 return
NA or an empty boundary.a5_line_string_to_cells() gains simplify:
with simplify = FALSE it returns an
a5_cell_list with 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 its
resolution argument is optional: by default each covering
expands to its own resolution. It refuses to build more than
.Machine$integer.max cells.a5_is_valid() now checks the index encoding. It used to
return TRUE for any non-missing a5_cell and
for any hex string that parsed, whether or not the value was a cell. Hex
strings are read as a5_cell() reads them, so unpadded ids,
as the a5 crate writes them, are accepted.a5_compact()’s first argument is now cell,
as in other functions that take cells, rather than
cells.a5_polygon_to_cells() accepts
containment = "center" as well as "centre",
and a5_spherical_cap() gains units, taking the
radius in any length unit or as a units object.a5R_error and a subclass naming what
went wrong, such as a5R_error_resolution or
a5R_error_geometry, so code can handle them without
matching messages. See ?a5_errors.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 its index with stored cells.a5R.h, see ?a5_c_api)
for packages that convert between 64-bit ids and a5_cell
vectors in compiled code, so they need not rely on how
a5_cell stores its data.a5_bbox() gives the longitude/latitude box of
polygons as A5 sees them, with great-circle edges that can bow beyond
the vertices’ latitudes.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) runs expr on
n threads and restores the previous count afterwards, as
with(mirai::daemons(n), expr) does.
a5_threads(n) returns the previous count as an
a5_threads object to make this work.a5_to_arrow_table() and
a5_from_arrow_table() convert data frames with cell and
covering columns to and from Arrow tables, recording the index version
under the schema metadata key a5_index and refusing tables
from another index.a5_migrate() converts ids from a5R before 1.0 to
the current index.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.a5_union(), a5_intersect() and
a5_difference() combine coverings element by element, and
a5_union(x) merges every covering in x;
a5_contains() tests cells against coverings (a
point-in-polygon test at the covering’s resolution) and
a5_overlaps() tests pairs of coverings;
a5_count() and a5_area() give the number of
cells and the area each covering stands for. Combining coverings at
different resolutions is an error, as upstream.a5_covering_stored() returns the compacted cells a
covering stores, without the compaction marker, for index range queries
with a5_cell_children_range().a5_covering_to_arrow() and
a5_covering_from_arrow() convert coverings to and from
Arrow list<uint64> with the compaction marker, the
layout DuckDB, Python and JavaScript A5 code read.a5_is_valid_cell() checks that values are A5 cells,
mirroring the upstream is_valid_cell.
a5_is_valid() becomes a generic that also accepts an
a5_cell_list, returning one value per element.a5_cell_to_boundary_xy() returns the boundary
vertices of many cells as flat x and y vectors
with per-cell lengths, ready for a single
grid::polygonGrob(). It skips building a geometry per cell,
so it is about 1.5 times faster than a5_cell_to_boundary()
followed by wk::wk_coords() on one thread, 3 times faster
on four, and allocates an eighth of the memory.As in the other A5 libraries,
a5_line_string_to_cells() gives the cell containing a
single waypoint, and a5_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() accepts
resolution = -1, giving the world cell.
The a5 crate 1.0.0 fixes two wrong results.
a5_polygon_to_cells() with
containment = "centre" no longer returns a whole quintant
of cells for thin sliver polygons, and a5_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, and a5_cell_to_lonlat() and
a5_cell_to_parent() up to a third faster, with lower peak
memory.
a5_lonlat_to_cell() returns NA for
NaN and 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()
and a5_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 after
a5_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 and NA, 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 a 0x prefix. A string
that is not a hex id is now an error instead of a silent
NA.
NA cells are missing to vctrs: ==
against NA gives NA, and sorting puts
NA last instead of among the resolution-30 cells.
as_a5_covering() treats NULL list
elements as missing coverings, so null coverings read back from Arrow
stay NA instead of becoming empty.
a5_polygon_to_cells() and
a5_line_string_to_cells() error on sf and
terra input 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 or NA
k, and a5_spherical_cap() a negative or
non-finite radius.
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 NA with a clear
error, and a5_get_num_children() errors when the child
resolution is coarser than the parent instead of returning 0.
a5_cell_from_arrow() and
a5_covering_from_arrow() give a clear error for input that
is not an Arrow array.
a5_cell_children_range() and
a5_cell_child() reach resolution 29 from any cell, as
documented, instead of erroring beyond 20 levels.
The world cell’s boundary is POLYGON EMPTY instead
of an invalid polygon.
a5_cell_to_parent() errors when
resolution is 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 a
segments value below 1 or NA; 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() handles wk::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, and
vignette("spatial-hierarchy") is rewritten for supercells
and subcells.
a5_cell_to_arrow(),
a5_cell_from_arrow(), a5_covering_to_arrow()
and a5_covering_from_arrow() run in Rust and pass one
contiguous uint64 buffer 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() and
a5_cell_children_range() follow the new order of
a5_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() and
a5_cell_to_subcells() move between resolutions by cell
centre rather than by index, wrapping the upstream
cell_to_supercell() and cell_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() and a5_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 with NA, subsetting with an
NA index, assigning NA,
vctrs::vec_init(), and unmatched rows from
merge(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 XOR 0xFC, so a filled-in gap decodes to the NA
sentinel, at no measurable cost. This changes the stored byte layout:
a5_cell objects saved with saveRDS(),
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 as
NA. 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()
and a5_spherical_cap() are vectorised over their cell
argument and gain a simplify argument. The default
simplify = TRUE returns one flat a5_cell
vector, so single-cell calls are unchanged;
simplify = FALSE returns a vctrs::list_of()
with one element per input, for list columns. An NA cell
now contributes no cells instead of raising an error.
New a5_cell_list class for those lists, with an
unlist() method that concatenates the elements into one
a5_cell vector. Base unlist() on a plain list
of a5_cell vectors returns a meaningless raw vector;
as_a5_cell_list() wraps such a list so
unlist() works.
Base R compatibility for a5_cell:
match() and %in% are about ten times faster
via an exact mtfrm() method instead of hex conversion;
x[i] <- value past the end and
length(x) <- n grow the vector with NA, so
rbind() on data frames with a5_cell columns
works. split() and tapply() with an
a5_cell grouping vector cannot be intercepted and give
wrong results; use vctrs::vec_split() or
vctrs::vec_group_id(), as documented in
vignette("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 exact BETWEEN filter 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, default
format, containment and method
arguments skip rlang::arg_match(),
a5_lonlat_to_cell() casts and recycles common inputs
without vctrs, a5_cell_to_lonlat() uses low-level
wk and data frame constructors, and
a5_cell_area(), a5_cell_edge_length_avg() and
a5_cell_distance() cache the parsed base unit. Scalar calls
are three to twenty times faster; results, recycling rules and error
messages are unchanged.
a5_polygon_to_cells(), a5_grid_disk(),
a5_spherical_cap() and a5_cell_to_lonlat().
Cell identifiers are unchanged: all existing results and snapshots are
bit-for-bit identical.a5_polygon_to_cells() gains a containment
argument. 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 removed
a5_grid().a5_cell_edge_length_avg() returns the average edge
length of a cell at a given resolution, as a units vector
(metres by default). Individual edges vary from the average by roughly
+/-10%.extendr-api
to 0.8.2 and passing the correct --target on Windows (#22,
@jeroen).a5_grid(), deprecated since 0.4.0. Use
a5_polygon_to_cells() instead. Note the semantics differ:
a5_grid() selected every cell a geometry touched (boundary
intersection), whereas a5_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).lifecycle R dependency and the
wkt Rust dependency, both of which were only needed by
a5_grid().geo Rust dependency with the
lightweight geographiclib-rs crate for
a5_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.EqualAreaProjection
refactor.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.a5_grid() is soft-deprecated in favour of
a5_polygon_to_cells(). Calling it now emits a
lifecycle::deprecate_warn() with guidance: use
a5_polygon_to_cells() for geometry inputs
(centre-in-polygon containment), or pass a wk::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), whereas
a5_polygon_to_cells() uses centre-point containment (cells
whose centroid lies inside).a5_polygon_to_cells() returns the A5 cells whose
centres lie inside a polygon. Distinct from a5_grid(),
which uses boundary-intersection semantics. Accepts wk-handleable
geometries (including MULTIPOLYGON and sfc of
several polygons), terra SpatVector objects, numeric
matrices, or data.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.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 as
a5_polygon_to_cells(), including
MULTILINESTRING, sfcs of multiple linestrings,
and terra SpatVector objects; per-feature outputs are
concatenated with first-seen deduplication.a5 Rust 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 in cell_to_lonlat (a5
0.7.3), faster cell_to_parent, and a polar-region spiral
fix in grid_disk and spherical_cap.a5_cell_to_lonlat() replaces
its normalise argument with as_dataframe
(default FALSE). When FALSE, centroids are
returned as a wk::xy() vector with WGS 84 CRS; when
TRUE, as a base data.frame with
lon/lat columns. The previous
normalise argument toggled longitude normalisation, but
upstream a5 (>= 0.7.3) always returns normalised longitudes, so the
flag’s effective job collapsed to “what container?”.
as_dataframe makes that explicit. Defaults are unchanged
for users who never set normalise (still returns
wk::xy); explicit
normalise = TRUE/FALSE calls now error and
must be updated.lifecycle dependency added to
Imports.a5_cell() now requires hex strings to be exactly 16
characters, preventing silent construction of wrong cells from truncated
input.a5_cell
representation and Arrow integration.a5_cell internal representation now uses 8 parallel raw
byte vectors instead of hex strings, reducing memory from ~80 bytes/cell
to 8 bytes/cell (#12).a5_cell gains vec_proxy_compare() and
vec_proxy_order() methods, enabling sort(),
order(), unique(), duplicated(),
match(), and %in% (#12).a5_cell gains an is.na() method
(#12).a5_cell_from_arrow() and
a5_cell_to_arrow() for lossless conversion between
a5_cell and Arrow uint64 arrays, enabling
zero-copy Parquet I/O (#12).a5_u64_to_hex() and a5_hex_to_u64()
for explicit conversion between a5_cell vectors and hex
strings.a5_is_cell() has been renamed to
a5_is_valid() and now accepts both a5_cell
vectors and character hex strings.a5_cell_distance() and a5_cell_area() gain
a units = NULL option to return plain numeric vectors
without units class overhead.vignette("internal-cell-representation")
and vignette("arrow-parquet").