h3o for H3 indexing

spatial
package
Published

March 16, 2023

{h3o} is an R package that offers high-performance geospatial indexing using the H3 grid system. The package is built using {extendr} and provides bindings to the Rust library of the same name.

The Rust community built h3o which is a pure rust implementation of Uber’s H3 hierarchical hexagon grid system. Since h3o is a pure rust library it is typically safer to use, just as fast, and dependency free.

Benefits of h3o

Since h3o is built purely in Rust and R it is system dependency free and can be compiled for multiple platforms including Linux, MacOS, and Windows, making it easy to use across different OS.

h3o benefits greatly from the type safety of Rust and provides robust error handling often returning 0 length vectors or NA values when appropriate where errors would typically occur using another H3 library.

And moreover, it is very fast!

Features

h3o supports all of the functionality that is provided by the C library and the Rust library h3o.

If there are any features missing, please make an issue on GitHub and I’ll be sure to address it!

h3o was built with sf objects and the tidyverse in mind. h3o objects can be created from sf objects and vice versa. Compatibility with the tidyverse is accomplished via the vctrs package.

  • sf::st_as_sfc() methods for H3 and H3Edge vectors
  • automatic nesting by creating lists of H3 and H3Edge vectors
    • vectorized output will never return more objects than inputs

Example

Create some points in the bounding box of Wyoming.

Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.1; sf_use_s2() is TRUE
library(h3o)

# create a bounding box
bbox_raw <- setNames(
  c(-111.056888, 40.994746, -104.05216, 45.005904),
  c("xmin", "ymin", "xmax", "ymax")
)

# create some points
pnts <- st_bbox(bbox_raw) |> 
  st_as_sfc() |> 
  st_set_crs(4326) |> 
  st_sample(25)

# convert to H3 index
hexs <- h3_from_points(pnts, 4) 
hexs
<H3[25]>
 [1] 8426b45ffffffff 8426a05ffffffff 8426a5dffffffff 8426b4dffffffff
 [5] 8426b15ffffffff 842896dffffffff 8426b43ffffffff 8426a05ffffffff
 [9] 84279bbffffffff 8426843ffffffff 8426a15ffffffff 8426ae5ffffffff
[13] 8426a63ffffffff 8426a3bffffffff 84278b5ffffffff 8426b11ffffffff
[17] 8426a4bffffffff 842685bffffffff 8426b19ffffffff 8426a35ffffffff
[21] 8426b15ffffffff 8426845ffffffff 8426b69ffffffff 84278b1ffffffff
[25] 84278b5ffffffff

The H3 vectors can be easily visualized by converting to sf objects. The st_as_sfc() method is defined for H3 vectors. While you may be familair with st_as_sf() the _sfc variant is used for creating columns and should be used on a vector not a dataframe. This way you can use it in a dplyr pipe.

polys <- st_as_sfc(hexs)
polys
Geometry set for 25 features 
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -110.8472 ymin: 40.83137 xmax: -103.6023 ymax: 44.86262
Geodetic CRS:  WGS 84
First 5 geometries:
POLYGON ((-109.9082 43.59055, -109.6438 43.7344...
POLYGON ((-105.9715 42.89294, -105.7036 43.0287...
POLYGON ((-104.3536 43.6947, -104.0817 43.82518...
POLYGON ((-109.6918 43.96129, -109.426 44.10402...
POLYGON ((-108.7011 41.9535, -108.4393 42.09726...

This can be plotted.

plot(polys)

To illustrate tidyverse compatibility lets create an sf object and create a column of H3 indexes.

library(dplyr, warn.conflicts = FALSE)

hexs <- tibble(geometry = pnts) |> 
  st_as_sf() |> 
  mutate(h3 = h3_from_points(geometry, 4))

hexs
Simple feature collection with 25 features and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -110.4469 ymin: 41.09064 xmax: -104.1631 ymax: 44.70057
Geodetic CRS:  WGS 84
# A tibble: 25 × 2
               geometry              h3
 *          <POINT [°]>            <H3>
 1 (-110.1512 43.82525) 8426b45ffffffff
 2 (-106.1227 43.14504) 8426a05ffffffff
 3 (-104.2611 43.84146) 8426a5dffffffff
 4  (-109.9657 44.0744) 8426b4dffffffff
 5 (-108.7754 42.18515) 8426b15ffffffff
 6 (-110.3885 44.12357) 842896dffffffff
 7 (-109.2388 43.58793) 8426b43ffffffff
 8 (-106.0339 42.94859) 8426a05ffffffff
 9 (-107.3991 44.60339) 84279bbffffffff
10 (-106.7301 41.27761) 8426843ffffffff
# … with 15 more rows

Afterwards, lets create a K = 3 disk around each grid cell, create a compact disk by compacting the cells, then unnest into a longer data frame, and update our geometries.

compact_hexs <- hexs |> 
  mutate(
    disks = grid_disk(h3, 3),
    compact_disks = purrr::map(disks, compact_cells)
  ) |> 
  tidyr::unnest_longer(compact_disks) |> 
  mutate(geometry = st_as_sfc(compact_disks)) |> 
  st_as_sf() 

Use ggplot2 to make a simple visualization.

library(ggplot2)

ggplot(compact_hexs) +
  geom_sf(fill = NA) +
  theme_void()