h3o

R-CMD-check CRAN status extendr

{h3o} is a lightweight R package for interacting with Uber’s H3 Geospatial Indexing system. The R package uses extendr to wrap the eponymous h3o Rust crate, which offers a pure Rust implementation of H3, so no linking to Uber’s H3 C library. The package is also intended to work with the {sf} package for geometric operations and as a bonus represents the H3 class as {vctrs}, so they work seamlessly within a tidyverse workflow.

Installation

You can install the release version of {h3o} from CRAN with:

install.packages("h3o")

Or you can install the development version from GitHub with:

# install.packages("pak")
pak::pak("extendr/h3o")

Example

H3 vectors can be created from POINT geometry columns (sfc objects) defined by sf.

library(h3o)
library(dplyr)
library(sf)
library(tibble)

xy <- data.frame(
  x = runif(100, -5, 10),
  y = runif(100, 40, 50)
)

pnts <- st_as_sf(
  xy,
  coords = c("x", "y"),
  crs = 4326
)

pnts |> mutate(h3 = h3_from_points(geometry, 5))
#> Simple feature collection with 100 features and 1 field
#> Geometry type: POINT
#> Dimension:     XY
#> Bounding box:  xmin: -4.906456 ymin: 40.13608 xmax: 9.720642 ymax: 49.77958
#> Geodetic CRS:  WGS 84
#> First 10 features:
#>                       geometry              h3
#> 1    POINT (6.604776 48.68961) 851f8423fffffff
#> 2  POINT (-0.6574923 40.42394) 85397237fffffff
#> 3   POINT (-4.327095 46.43766) 851843dbfffffff
#> 4    POINT (2.098711 40.28164) 85394283fffffff
#> 5    POINT (8.558989 44.79674) 851f9bd7fffffff
#> 6   POINT (-2.064705 47.93067) 8518637bfffffff
#> 7    POINT (7.966344 47.88122) 851f8143fffffff
#> 8    POINT (-1.18493 48.41385) 85186383fffffff
#> 9   POINT (0.2382595 43.76685) 8539668ffffffff
#> 10  POINT (0.2054317 48.94255) 85186573fffffff

H3 vectors also have an st_as_sfc() method which allows conversion of H3 cell indexes into sf POLYGONs.

# replace geometry
h3_cells <- pnts |>
  mutate(
    h3 = h3_from_points(geometry, 4),
    geometry = st_as_sfc(h3)
  )

# plot the hexagons
plot(st_geometry(h3_cells))

H3 cell centroids can be returned using h3_to_points(). If sf is avilable, the results will be returned as an sfc (sf column) object. Otherwise it will return a list of sfg (sf geometries).

# fetch h3 column
h3s <- h3_cells[["h3"]]

# get there centers
h3_centers <- h3_to_points(h3s)

# plot the hexagons with the centers
plot(st_geometry(h3_cells))
plot(h3_centers, pch = 16, add = TRUE, col = "black")

H3Edge vectors representing the boundaries of H3 cells can be created with h3_edges(), h3_shared_edge_pairwise(), and h3_shared_edge_sparse().

cell_edges <- h3_edges(h3s[1:3])
cell_edges
#> [[1]]
#> <H3Edge[6]>
#> [1] 1141f843ffffffff 1241f843ffffffff 1341f843ffffffff 1441f843ffffffff
#> [5] 1541f843ffffffff 1641f843ffffffff
#> 
#> [[2]]
#> <H3Edge[6]>
#> [1] 11439723ffffffff 12439723ffffffff 13439723ffffffff 14439723ffffffff
#> [5] 15439723ffffffff 16439723ffffffff
#> 
#> [[3]]
#> <H3Edge[6]>
#> [1] 1141843dffffffff 1241843dffffffff 1341843dffffffff 1441843dffffffff
#> [5] 1541843dffffffff 1641843dffffffff

We’ve created a list of each cell’s edges. We can flatten them using flatten_edges().

cell_edges <- flatten_edges(cell_edges)
cell_edges
#> <H3Edge[18]>
#>  [1] 1141f843ffffffff 1241f843ffffffff 1341f843ffffffff 1441f843ffffffff
#>  [5] 1541f843ffffffff 1641f843ffffffff 11439723ffffffff 12439723ffffffff
#>  [9] 13439723ffffffff 14439723ffffffff 15439723ffffffff 16439723ffffffff
#> [13] 1141843dffffffff 1241843dffffffff 1341843dffffffff 1441843dffffffff
#> [17] 1541843dffffffff 1641843dffffffff

These can be cast to sfc objects using st_as_sfc().

st_as_sfc(cell_edges)
#> Geometry set for 18 features 
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: -4.577141 ymin: 40.10303 xmax: 6.908627 ymax: 48.92561
#> Geodetic CRS:  WGS 84
#> First 5 geometries:
#> LINESTRING (6.828196 48.56718, 6.908627 48.78762)
#> LINESTRING (6.24713 48.62253, 6.49798 48.48486)
#> LINESTRING (6.49798 48.48486, 6.828196 48.56718)
#> LINESTRING (6.656859 48.92561, 6.325574 48.84283)
#> LINESTRING (6.908627 48.78762, 6.656859 48.92561)

Additionally, you can get the vertexes of H3 cell indexes using h3_to_vertexes() which returns an sfc_MULTIPOINT.

h3_to_vertexes(h3s)
#> Geometry set for 100 features 
#> Geometry type: MULTIPOINT
#> Dimension:     XY
#> Bounding box:  xmin: -5.162291 ymin: 40.02598 xmax: 10.1164 ymax: 50.08041
#> Geodetic CRS:  WGS 84
#> First 5 geometries:
#> MULTIPOINT ((6.325574 48.84283), (6.24713 48.62...
#> MULTIPOINT ((-0.6690999 40.57641), (-0.9475784 ...
#> MULTIPOINT ((-4.227092 46.53696), (-4.535994 46...
#> MULTIPOINT ((1.905903 40.46739), (1.849434 40.2...
#> MULTIPOINT ((8.142693 44.78613), (8.062234 44.5...

Bench marks

Since h3o is written in Rust, it is very fast.

Creating polygons

h3_strs <- as.character(h3s)
bench::mark(
  h3o = st_as_sfc(h3s),
  h3jsr = h3jsr::cell_to_polygon(h3_strs)
)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 h3o         462.6µs  505.7µs     1863.    9.85KB     14.6
#> 2 h3jsr        8.34ms   9.36ms      106.     2.7MB     90.0

Converting polygons to H3 cells:

nc <- st_read(system.file("gpkg/nc.gpkg", package = "sf"), quiet = TRUE) |>
  st_transform(4326) |>
  st_geometry()

bench::mark(
  h3o = sfc_to_cells(nc, 5, "centroid"),
  h3jsr = h3jsr::polygon_to_cells(nc, 5),
  check = FALSE
)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 h3o           4.8ms   5.35ms     183.     21.4KB    11.3 
#> 2 h3jsr        27.6ms  28.51ms      34.8   748.7KB     4.97

Converting points to cells

bench::mark(
  h3o = h3_from_points(pnts$geometry, 3),
  h3jsr = h3jsr::point_to_cell(pnts$geometry, 3),
  check = FALSE
)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 h3o         103.4µs  123.3µs     7372.      848B     11.4
#> 2 h3jsr        2.63ms   3.04ms      326.     975KB     13.3

Retrieve edges

bench::mark(
  h3o = h3_edges(h3s),
  h3jsr = h3jsr::get_udedges(h3_strs),
  check = FALSE
)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 h3o         403.5µs  530.3µs     1593.      848B     16.9
#> 2 h3jsr        1.64ms   1.76ms      555.    67.9KB     17.3

Get origins and destinations from edges.

# get edges for a single location
eds <- h3_edges(h3s[1])[[1]]
# strings for h3jsr
eds_str <- as.character(eds)

bench::mark(
  h3o = h3_edge_cells(eds),
  h3jsr = h3jsr::get_udends(eds_str),
  check = FALSE
)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 h3o          12.9µs   18.1µs    49775.    7.86KB     19.9
#> 2 h3jsr       631.3µs  740.1µs     1248.   19.82KB     12.9