The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
hexify v0.5.0 adds H3 as a
first-class grid type alongside ISEA. Every core function —
hexify(), grid_rect(),
grid_clip(), get_parent(),
get_children() — works with both grid systems through the
same interface. This vignette covers what H3 is, how to use it in
hexify, and when to prefer it over ISEA.
H3 is a hierarchical hexagonal grid system developed by Uber. It
partitions Earth’s surface into hexagonal cells at 16 resolutions
(0–15), each roughly 7\(\times\) finer
than the last. Cell IDs are 64-bit integers encoded as hexadecimal
strings (e.g., "8528342bfffffff").
H3 has become an industry standard adopted by the FCC, Foursquare, and numerous geospatial platforms.
Key difference from ISEA: H3 cells are not
equal-area. Cell area varies by ~1.6\(\times\) between the largest and smallest
hexagons at any given resolution, depending on latitude. For rigorous
equal-area analysis, use ISEA. For interoperability with H3 ecosystems,
use type = "h3".
Create an H3 grid by passing type = "h3" to
hex_grid():
library(sf)
#> Linking to GEOS 3.13.1, GDAL 3.11.4, PROJ 9.7.0; sf_use_s2() is TRUE
library(ggplot2)
# Create an H3 grid specification
grid_h3 <- hex_grid(resolution = 5, type = "h3")
#> H3 cells are not exactly equal-area; area varies ~3-5% by latitude.
#> This message is displayed once per session.
grid_h3
#> HexGridInfo Specification [H3]
#> -------------------------------
#> Grid Type: H3 (Uber)
#> Resolution: 5
#> Avg Area: 252.9040 km^2 (varies by location)
#> Avg Diagonal:17.09 km
#> CRS: EPSG:4326
#> Total Cells: 2016842
#> Note: H3 cells are NOT exactly equal-areaThen use hexify() with the grid object, just like
ISEA:
# Sample cities
cities <- data.frame(
name = c("Vienna", "Paris", "Madrid", "Berlin", "Rome",
"London", "Prague", "Warsaw", "Budapest", "Amsterdam"),
lon = c(16.37, 2.35, -3.70, 13.40, 12.50,
-0.12, 14.42, 21.01, 19.04, 4.90),
lat = c(48.21, 48.86, 40.42, 52.52, 41.90,
51.51, 50.08, 52.23, 47.50, 52.37)
)
result <- hexify(cities, lon = "lon", lat = "lat", grid = grid_h3)
result
#> HexData Object
#> --------------
#> Rows: 10
#> Columns: 3
#> Cells: 10 unique
#> Type: data.frame
#>
#> Grid:
#> H3 Resolution 5 (~252.9040 km^2 avg)
#>
#> Columns: name, lon, lat
#>
#> Data preview (with cell assignments):
#> name lon lat cell_id
#> Vienna 16.37 48.21 851e15b7fffffff
#> Paris 2.35 48.86 851fb467fffffff
#> Madrid -3.70 40.42 85390ca3fffffff
#> ... with 7 more rowsH3 cell IDs are character strings, unlike ISEA’s numeric IDs:
# Cell IDs are hexadecimal strings
result@cell_id
#> [1] "851e15b7fffffff" "851fb467fffffff" "85390ca3fffffff" "851f1d4bfffffff"
#> [5] "851e8053fffffff" "85194ad3fffffff" "851e3543fffffff" "851f53cbfffffff"
#> [9] "851e037bfffffff" "85196953fffffff"
# All standard accessors work
cells(result)
#> [1] "851e15b7fffffff" "851fb467fffffff" "85390ca3fffffff" "851f1d4bfffffff"
#> [5] "851e8053fffffff" "85194ad3fffffff" "851e3543fffffff" "851f53cbfffffff"
#> [9] "851e037bfffffff" "85196953fffffff"
n_cells(result)
#> [1] 10If you think in terms of cell area rather than resolution numbers,
pass area_km2 instead of resolution. hexify
picks the closest H3 resolution:
grid_area <- hex_grid(area_km2 = 500, type = "h3")
#> Warning in hex_grid(area_km2 = 500, type = "h3"): H3 cells are not exactly
#> equal-area. Closest resolution 5 has average area ~252.904 km^2 (requested
#> 500.000 km^2)
grid_area
#> HexGridInfo Specification [H3]
#> -------------------------------
#> Grid Type: H3 (Uber)
#> Resolution: 5
#> Avg Area: 252.9040 km^2 (varies by location)
#> Avg Diagonal:17.09 km
#> CRS: EPSG:4326
#> Total Cells: 2016842
#> Note: H3 cells are NOT exactly equal-areaAll grid generation functions work with H3 grids.
# Generate H3 hexagons over Western Europe
grid_h3 <- hex_grid(resolution = 3, type = "h3")
europe_h3 <- grid_rect(c(-10, 35, 25, 60), grid_h3)
# Basemap
europe <- hexify_world[hexify_world$continent == "Europe", ]
ggplot() +
geom_sf(data = europe, fill = "gray95", color = "gray60") +
geom_sf(data = europe_h3, fill = NA, color = "#E6550D", linewidth = 0.4) +
coord_sf(xlim = c(-10, 25), ylim = c(35, 60)) +
labs(title = sprintf("H3 Resolution %d Grid (~%.0f km² avg cells)",
grid_h3@resolution, grid_h3@area_km2)) +
theme_minimal()# Clip H3 grid to France
france <- hexify_world[hexify_world$name == "France", ]
grid_h3 <- hex_grid(resolution = 4, type = "h3")
france_h3 <- grid_clip(france, grid_h3)
#> Spherical geometry (s2) switched off
#> although coordinates are longitude/latitude, st_intersection assumes that they
#> are planar
#> Spherical geometry (s2) switched on
ggplot() +
geom_sf(data = france, fill = "gray95", color = "gray40", linewidth = 0.5) +
geom_sf(data = france_h3, fill = alpha("#E6550D", 0.3),
color = "#E6550D", linewidth = 0.3) +
coord_sf(xlim = c(-5, 10), ylim = c(41, 52)) +
labs(title = sprintf("H3 Grid Clipped to France (res %d)", grid_h3@resolution)) +
theme_minimal()The standard hexify workflow applies to H3 grids. Here’s a complete example using simulated species observations:
set.seed(42)
# Simulate observations across Europe
obs <- data.frame(
lon = c(rnorm(200, 10, 12), rnorm(100, 25, 8)),
lat = c(rnorm(200, 48, 6), rnorm(100, 55, 4)),
species = sample(c("Sp. A", "Sp. B", "Sp. C"), 300, replace = TRUE)
)
obs$lon <- pmax(-10, pmin(40, obs$lon))
obs$lat <- pmax(35, pmin(65, obs$lat))
# Hexify with H3
grid_h3 <- hex_grid(resolution = 3, type = "h3")
obs_hex <- hexify(obs, lon = "lon", lat = "lat", grid = grid_h3)
# Aggregate: species richness per cell
obs_df <- as.data.frame(obs_hex)
obs_df$cell_id <- obs_hex@cell_id
richness <- aggregate(species ~ cell_id, data = obs_df,
FUN = function(x) length(unique(x)))
names(richness)[2] <- "n_species"
# Map it
polys <- cell_to_sf(richness$cell_id, grid_h3)
polys <- merge(polys, richness, by = "cell_id")
europe <- hexify_world[hexify_world$continent == "Europe", ]
ggplot() +
geom_sf(data = europe, fill = "gray95", color = "gray70", linewidth = 0.2) +
geom_sf(data = polys, aes(fill = n_species), color = "white", linewidth = 0.3) +
scale_fill_viridis_c(option = "plasma", name = "Species\nRichness") +
coord_sf(xlim = c(-10, 40), ylim = c(35, 65)) +
labs(title = "Species Richness on H3 Grid",
subtitle = sprintf("H3 resolution %d (~%.0f km² avg cells)",
grid_h3@resolution, grid_h3@area_km2)) +
theme_minimal() +
theme(axis.text = element_blank(), axis.ticks = element_blank())hexify v0.6.0 added h3_crosswalk() for bidirectional
mapping between ISEA and H3 cell IDs. This is useful when you work in
ISEA for analysis but need to share results with H3 ecosystems (or vice
versa).
# Start with an ISEA grid and some cells
grid_isea <- hex_grid(resolution = 9, aperture = 3)
isea_ids <- lonlat_to_cell(
lon = c(16.37, 2.35, 13.40, -3.70, 12.50),
lat = c(48.21, 48.86, 52.52, 40.42, 41.90),
grid = grid_isea
)
# Map ISEA cells to their closest H3 equivalents
xw <- h3_crosswalk(isea_ids, grid_isea)
xw[, c("isea_cell_id", "h3_cell_id", "isea_area_km2", "h3_area_km2")]
#> isea_cell_id h3_cell_id isea_area_km2 h3_area_km2
#> 1 42280 841e15bffffffff 2591.375 1753.173
#> 2 39597 841fb43ffffffff 2591.375 1569.204
#> 3 40823 841f1d5ffffffff 2591.375 1570.136
#> 4 39585 84390cbffffffff 2591.375 1819.493
#> 5 42516 841e80dffffffff 2591.375 1885.191The area_ratio column shows how ISEA and H3 cell sizes
compare — values close to 1 mean the resolutions are well-matched.
| ISEA | H3 | |
|---|---|---|
| Cell area | Exactly equal | ~1.6\(\times\) variation |
| Cell IDs | Numeric (integer) | Character (hex string) |
| Apertures | 3, 4, 7, 4/3 | Fixed (7) |
| Resolutions | 0–30 | 0–15 |
| Hierarchy | Approximate (aperture-dependent) | Exact (7 children per parent) |
| Dependencies | None (built-in C++) | None (vendored H3 C library) |
| Industry adoption | Scientific / government | Tech industry / commercial |
Use ISEA when:
Equal-area cells are required (biodiversity surveys, density estimation, statistical sampling)
You need fine control over aperture and resolution
No external dependencies are acceptable
Use H3 when:
Interoperability with H3 ecosystems (Uber, Foursquare, DuckDB, BigQuery)
Clean hierarchical operations (parent/child traversal) are a priority
Slight area variation across latitudes is acceptable for your analysis
h3_res <- hexify_compare_resolutions(type = "h3", res_range = 0:15)
h3_res$n_cells_fmt <- ifelse(
h3_res$n_cells > 1e9,
sprintf("%.1fB", h3_res$n_cells / 1e9),
ifelse(h3_res$n_cells > 1e6,
sprintf("%.1fM", h3_res$n_cells / 1e6),
ifelse(h3_res$n_cells > 1e3,
sprintf("%.1fK", h3_res$n_cells / 1e3),
as.character(h3_res$n_cells)))
)
knitr::kable(
h3_res[, c("resolution", "n_cells_fmt", "cell_area_km2", "cell_spacing_km")],
col.names = c("Resolution", "# Cells", "Avg Area (km²)", "Spacing (km)"),
digits = 1
)| Resolution | # Cells | Avg Area (km²) | Spacing (km) |
|---|---|---|---|
| 0 | 122 | 4357449.4 | 2243.1 |
| 1 | 842 | 609788.4 | 839.1 |
| 2 | 5.9K | 86801.8 | 316.6 |
| 3 | 41.2K | 12393.4 | 119.6 |
| 4 | 288.1K | 1770.3 | 45.2 |
| 5 | 2.0M | 252.9 | 17.1 |
| 6 | 14.1M | 36.1 | 6.5 |
| 7 | 98.8M | 5.2 | 2.4 |
| 8 | 691.8M | 0.7 | 0.9 |
| 9 | 4.8B | 0.1 | 0.3 |
| 10 | 33.9B | 0.0 | 0.1 |
| 11 | 237.3B | 0.0 | 0.0 |
| 12 | 1661.0B | 0.0 | 0.0 |
| 13 | 11626.7B | 0.0 | 0.0 |
| 14 | 81386.8B | 0.0 | 0.0 |
| 15 | 569707.4B | 0.0 | 0.0 |
Areas are averages — actual cell area varies by latitude.
vignette("quickstart") - Getting started with hexify
(ISEA-focused)
vignette("workflows") - Grid generation,
multi-resolution analysis, GIS export
vignette("visualization") - Plotting with
plot(), hexify_heatmap()
vignette("theory") - Mathematical foundations of the
ISEA projection
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.