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.0; 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] 8428967ffffffff 8426b39ffffffff 8426a63ffffffff 8426b07ffffffff
 [5] 8426a39ffffffff 8426b63ffffffff 8426b0bffffffff 8426b41ffffffff
 [9] 842799bffffffff 8426b37ffffffff 8426b69ffffffff 842686bffffffff
[13] 8426a23ffffffff 8426b27ffffffff 8426a33ffffffff 8426b37ffffffff
[17] 8427995ffffffff 8426ae1ffffffff 8426b41ffffffff 8426b19ffffffff
[21] 8427987ffffffff 8426a3dffffffff 8426869ffffffff 8426a4bffffffff
[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: -111.3223 ymin: 40.98183 xmax: -104.0128 ymax: 45.25314
Geodetic CRS:  WGS 84
First 5 geometries:
POLYGON ((-110.4193 44.57636, -110.1531 44.7197...
POLYGON ((-109.2683 41.89614, -109.0075 42.0412...
POLYGON ((-106.3176 43.2172, -106.0491 43.35331...
POLYGON ((-109.6219 42.21408, -109.3608 42.3595...
POLYGON ((-105.2868 42.23801, -105.0199 42.3733...

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: -111.0541 ymin: 41.15866 xmax: -104.2527 ymax: 44.95915
Geodetic CRS:  WGS 84
# A tibble: 25 × 2
               geometry              h3
 *          <POINT [°]>            <H3>
 1 (-110.5547 44.90545) 8428967ffffffff
 2 (-109.1383 42.13601) 8426b39ffffffff
 3 (-106.4981 43.56823) 8426a63ffffffff
 4 (-109.6964 42.39072) 8426b07ffffffff
 5   (-105.276 42.5727) 8426a39ffffffff
 6 (-110.1881 43.04803) 8426b63ffffffff
 7 (-108.7864 42.90369) 8426b0bffffffff
 8 (-109.6165 43.81228) 8426b41ffffffff
 9 (-105.9806 44.87154) 842799bffffffff
10  (-109.811 41.48857) 8426b37ffffffff
# ℹ 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()