---
code-fold: true
---
# H3 Grid System
```{julia}
using GlobalGrids, GeoMakie, CairoMakie
grid = H3Grid()
fig = Figure()
ax = GeoAxis(fig[1,1], dest = "+proj=ortho +lon_0=-50 +lat_0=30")
lines!(ax, [grid[i] for i in 1:121])
poly!(ax, collect(GlobalGrids.pentagons(grid, 0)), color=(:black, .4))
ep = surface!(ax,
-180..180, -90..90,
zeros(axes(rotr90(GeoMakie.earth())));
shading = NoShading, color = rotr90(GeoMakie.earth())
)
translate!(ep, 0, 0, -1)
fig
```
H3 is a DGGS developed by Uber. Here's a brief summary of the H3 grid system:
- There are 16 resolutions (0-15 coarse-to-fine).
- At each resolution, there are exactly 12 pentagon cells in the grid. Every other cell is a hexagon.
- Pentagon locations are determined such that their centers are not on land.
- Refer to [https://h3geo.org/docs](https://h3geo.org/docs) for more information.
## Creating an `H3Cell`
- Every cell in the grid is indexed by a single `UInt64`. An `H3Cell` is simply a wrapper around a `UInt64` index.
- The index encodes the base cell (121 possible values, shown in the figure above), resolution, and *digits* (0-6).
- Each hexagon gets split into 7 hexagons by the next highest resolution. An additional digit corresponds to one of the smaller hexagons derived from its parent.
### From `H3Grid`
```{julia}
#| code-fold: false
grid = H3Grid()
# Select one of the 121 base cells:
grid[121]
```
```{julia}
#| code-fold: false
# Nesting getindex to dive down the hierarchy:
grid[121][1][5][0]
```
### From Base Cell and Digits
```{julia}
#| code-fold: false
H3Cell(121, [1, 5, 0])
```
### From Coordinates and Resolution:
```{julia}
#| code-fold: false
coords = LonLat(-75.0, 54.0)
H3Cell(coords, 10)
```
### From Hexadecimal String
```{julia}
#| code-fold: false
c = H3Cell(coords)
str = GlobalGrids.h3_string(c)
@info str
H3Cell(str)
```
## Inspecting H3Cells
```{julia}
@show GlobalGrids.resolution(c)
@show GlobalGrids.h3_base_cell(c)
@show GlobalGrids.is_pentagon(c)
@show GlobalGrids.h3_digits(c)
@show GlobalGrids.h3_face_numbers(c)
nothing
```
## GeoInterface Integration:
```{julia}
import GeoInterface as GI
@show GI.coordinates(c)
@show GI.centroid(c)
@show GI.area(c) # meters ^ 2
nothing
```
## Grid Hierarchy
- `parent`, `siblings`, and `children`
```{julia}
fig = Figure()
ax = Axis(fig[1,1], title="Cell:Black, Parent:Blue, Siblings:Orange, Children:Green")
lines!(ax, GlobalGrids.parent(c), color = :blue)
poly!(ax, c, color = :black)
lines!(ax, GlobalGrids.siblings(c), color = :orange)
lines!(ax, GlobalGrids.children(c), color = :green)
fig
```
## Rings and Disks
- `grid_ring(cell, i)` returns the cells that are exactly grid distance `i` away from the `cell`.
- `grid_disk(cell, i)` is similar to `grid_ring`, but returns all cells inside the ring as well.
```{julia}
fig = Figure()
ax = Axis(fig[1,1])
colors = [:blue, :green, :red, :magenta]
poly!(ax, c, color=:black)
for i in 1:4
poly!(ax, GlobalGrids.grid_ring(c, i), color=colors[i])
end
fig
```
## "Rasterizing" Geometries to the H3 Grid
### Point
```{julia}
ll = LonLat(-54.0, -12.0)
fig = Figure()
ax = Axis(fig[1,1], title="Cells Containing Point at Resolutions 0-3")
for res in 0:3
lines!(ax, h3cells(ll, res))
end
scatter!(ax, ll)
fig
```
### MultiPoint
```{julia}
coords = [LonLat(360rand() - 180, 180rand() - 90) for _ in 1:100]
x = H3Cell.(coords, 0)
f, a, p = lines(x, axis=(;type=GeoAxis, dest = "+proj=ortho +lon_0=-50 +lat_0=30"))
scatter!(a, coords, color=:black)
f
```
### Line
- By default, the cells will be an arbitrary shortest path between the cells that contain the two points (`containment = :shortest_path`).
- The other containment options for line(string)s are `:overlap` and `:overlap_bbox`.
```{julia}
line = GI.Line([LonLat(0, 0), LonLat(1,1)])
fig = Figure(size=(1000, 300))
for (i, containment) in enumerate((:shortest_path, :overlap, :overlap_bbox))
ax = Axis(fig[1, i], aspect=DataAspect(), title="containment = :$containment")
x = h3cells(line, 6; containment)
lines!(ax, x)
lines!(ax, GI.coordinates(line), linewidth=3, color = :black)
end
fig
```
### (Multi)Polygon
- Containment modes for polygons:
- `:center`: Include cell if its center is inside the polygon.
- `:full`: Include cell if all vertices are inside the polygon.
- `:overlap`: Include cell if any part intersects the polygon.
- `:overlap_bbox`: Include cell if any part of the bounding box intersects the polygon.
```{julia}
using OSMGeocoder
obj = geocode(state="North Carolina")
# Create figure
function make_axis(i, j, containment)
ax = Axis(fig[i, j], title="containment = :$containment")
x = h3cells(obj.geometry[1], 4; containment)
lines!(ax, x)
lines!(ax, obj.geometry)
end
fig = Figure()
make_axis(1, 1, :center)
make_axis(1, 2, :full)
make_axis(2, 1, :overlap)
make_axis(2, 2, :overlap_bbox)
fig
```
### Extents
:::{.callout-note}
The same `containment` options for (multi)polygons are available for Extents. test
:::
```{julia}
ex = GI.extent(obj)
# for plotting
x1, x2 = ex.X
y1, y2 = ex.Y
linestring = GI.LineString([(x1,y1), (x1,y2), (x2,y2), (x2, y1), (x1,y1)])
# Create figure
function make_axis(i, j, containment)
ax = Axis(fig[i, j], title="containment = :$containment")
x = h3cells(ex, 4; containment)
lines!(ax, linestring)
lines!(ax, x)
end
fig = Figure()
make_axis(1, 1, :center)
make_axis(1, 2, :full)
make_axis(2, 1, :overlap)
make_axis(2, 2, :overlap_bbox)
fig
```
## "Rasterizing" Rasters
- For data that is associated with a location, i.e. rasters, `cells` creates a `Dict{H3Cell, Vector{T}}` where `T` is the eltype of the data.
- Note that plotting polygons across a left/right boundary on a given projection can have strange behavior. See [https://en.wikipedia.org/wiki/180th_meridian#Software_representation_problems](https://en.wikipedia.org/wiki/180th_meridian#Software_representation_problems).
```{julia}
import Scratch
ENV["RASTERDATASOURCES_PATH"] = Scratch.get_scratch!("RASTERDATASOURCES_PATH")
nothing
```
```{julia}
using GlobalGrids, Rasters, RasterDataSources, ArchGDAL
r = Raster(getraster(WorldClim{Elevation}).elev)
dict = h3cells(r, 2)
cells = collect(keys(dict))
vals = maximum.(values(dict))
# Do some work to avoid polygon issues on boundaries:
idx = findall(x -> !GlobalGrids.crosses_meridian(x, 180), cells)
cells2 = cells[idx]
vals2 = vals[idx]
fig = Figure()
ax1 = Axis(fig[1, 1], title="Without Polygon Filtering")
ax2 = Axis(fig[2, 1], title="With Polygon Filtering")
poly!(ax1, cells, color=vals)
poly!(ax2, cells2, color=vals2)
fig
```
:::{.callout-note}
When plotting large numbers of cells, its more efficient to just plot the centroid of the cell:
:::
```{julia}
dict = h3cells(r, 4)
cells = collect(keys(dict))
vals = maximum.(values(dict))
scatter(GI.centroid.(cells), color=vals, markersize=2)
```