Appendix

Everything in this document can be rebuilt from the files in its project folder. This appendix lists them: the data, the scripts that made the data, the functions the chapters define, and the software they run on. Chapter 10 argued that a map made from points and rules is worth keeping because it can be rebuilt. This is the same idea applied to the document itself.

Show the code
## --- Standard packages ---

## many useful functions, including read_csv()
library(tidyverse)
## formatted tables
library(gt)

## --- Options and defaults ---

## suppress read_csv() column-type messages
options(readr.show_col_types = FALSE)

Data files

All the data files are in the data folder. Most are small, so any chapter can be run on its own.

Show the code
## the list of data files, with their sources
data_files <- read_csv("data/appendix_data_files.csv")

## add the size of each file, in kilobytes
data_files <- mutate(data_files, kb = round(file.size(file.path("data", file)) / 1024))

## show it
data_files |>
  select(file, contents, source, kb, made_by, chapters) |>
  gt() |>
  cols_label(file = "File", contents = "Contents", source = "Source", kb = "Size (KB)",
             made_by = "Made by", chapters = "Chapters") |>
  tab_style(style = cell_text(font = "monospace", size = "small"), locations = cells_body(columns = c(file, made_by)))
File Contents Source Size (KB) Made by Chapters
kipuka_puaulu_releves.csv 12 demonstration points: ID, UTM easting and northing, vegetation type Points placed 3 per canopy structure class; types read from Mueller-Dombois and Fosberg (1974) 1 prototype/canopy_voronoi_prototype_v1.R 2-9
kipuka_puaulu_study_area.csv Corners of the 3.2 by 2.6 km study-area rectangle (UTM zone 5N) Centered on photographs taken in the kīpuka 0 prototype/canopy_voronoi_prototype_v1.R 2-10
kipuka_puaulu_outline.geojson Outline of Kīpuka Puaulu (94.5 ha) Traced from canopy height over 8 m 293 Chapter 8 (the tracing code) 3, 6, 8, 9
kipuka_puaulu_specimens.csv 164 herbarium specimens with species, family, status, year, herbarium and coordinates GBIF preserved specimens within 2 km of the kīpuka, accessed 2026-07-05, one sheet per gathering 14 the checklists project (Projects/checklists) 6
kipuka_puaulu_canopy_height.tif Canopy height in 5 m cells for the study area Meta and World Resources Institute global 1 m canopy height map (Tolan et al. 2024) 322 R/make_kipuka_canopy_data.R 8-9
mdf1974_classes.tif The 1974 vegetation map as classes in 5 m cells Mueller-Dombois and Fosberg (1974), sheets 12 and 13, digitized 9 R/make_mdf1974_data.R 9
mdf1974_classes.csv Names of the 1974 classes Mueller-Dombois and Fosberg (1974) 0 R/make_mdf1974_data.R 9
la_gardens.csv Five Los Angeles basin botanical gardens: city, area, location, Wikipedia article and 2024 pageviews Wikipedia; OpenStreetMap (Nominatim); Wikimedia pageviews API 1 R/make_la_gardens_data.R 7
la_land_frame.geojson Land frame for the Los Angeles basin U.S. Census Bureau county boundaries, 2020 (via tigris) 14 R/make_la_gardens_data.R 7
la_tracts_2020.csv 3,207 census tracts: ID, 2020 population, a point inside each tract U.S. Census Bureau, 2020 Census (P1_001N, via tidycensus) 115 R/make_la_gardens_data.R 7

Scripts that make the data

Three of the data files come from scripts in the R folder. Each one runs once, from the project folder, and writes its files into data. The chapters read the files; they don’t run the scripts.

Show the code
## the scripts, what they make, and what they need
scripts <- read_csv(file =
"script,makes,needs
R/make_kipuka_canopy_data.R,kipuka_puaulu_canopy_height.tif,an internet connection (no key)
R/make_mdf1974_data.R,mdf1974_classes.tif and mdf1974_classes.csv,the digitized map sheets in expert_map/ and the canopy file (for its grid)
R/make_la_gardens_data.R,la_gardens.csv; la_land_frame.geojson; la_tracts_2020.csv,an internet connection and a Census API key
R/fig_ch1_voronoi.R,the Voronoi map in Chapter 1,the points and study area files")

## show it
scripts |>
  gt() |>
  cols_label(script = "Script", makes = "Makes", needs = "Needs") |>
  tab_style(style = cell_text(font = "monospace", size = "small"), locations = cells_body(columns = script))
Script Makes Needs
R/make_kipuka_canopy_data.R kipuka_puaulu_canopy_height.tif an internet connection (no key)
R/make_mdf1974_data.R mdf1974_classes.tif and mdf1974_classes.csv the digitized map sheets in expert_map/ and the canopy file (for its grid)
R/make_la_gardens_data.R la_gardens.csv; la_land_frame.geojson; la_tracts_2020.csv an internet connection and a Census API key
R/fig_ch1_voronoi.R the Voronoi map in Chapter 1 the points and study area files

Functions in the chapters

Each chapter defines the functions it needs, so that each chapter runs on its own. That means some functions appear in more than one chapter. The table gathers them in the order the document uses them. The tessellr package, in the next section, gathers the same ideas in general form.

Show the code
## the functions defined in the chapters
functions <- read_csv("data/appendix_functions.csv")

## show them, grouped by stage
functions |>
  gt(groupname_col = "stage") |>
  cols_label(fun = "Function", arguments = "Arguments", purpose = "Purpose", chapters = "Chapters") |>
  tab_style(style = cell_text(font = "monospace", size = "small"), locations = cells_body(columns = c(fun, arguments))) |>
  tab_style(style = cell_text(weight = "bold"), locations = cells_row_groups())
Function Arguments Purpose Chapters
Building tiles
make_tiles points, frame Build Voronoi tiles around the points, trim them to the frame, and attach each point's attributes to its tile 3-6
Reading tiles
pattern_tiles pts, label Build tiles for one point pattern and flag the interior tiles 5
cv_of tiles Coefficient of variation of interior tile area 5
pattern_map tiles, title Map one pattern, shaded by tile area 5
Tiles as containers
count_tiles tiles, points Count the points (such as specimens) that fall in each tile 6
Weighted tiles
winning_garden xy, weights For each location, the garden with the smallest distance divided by weight 7
service_map weights Service areas on a 500 m grid of land cells 7
people_won weights People living in each garden's service area, from census tracts 7
draw_service areas, title Draw one service-area map 7
Adding the canopy
nearest_type px, py, type, point_class, constrained Plain or structure-constrained Voronoi on a raster grid 8-9
map_polys, type_polys a raster of types Turn a raster of vegetation types into polygons for drawing 8-9
draw_veg, draw_with_lines a raster of types, title Draw a vegetation map, with or without the 1974 lines 8-9
Checking the map
area_agree v Share of the area with the same type as the 1974 map 9
edges_of v Boundary cells of a map 9
boundary_agree v Share of the 1974 boundary within 50 m of the map's boundary, the reverse, and the length drawn 9

The tessellr package

The code in the chapters is written to teach. Each chapter repeats what it needs, and some of it is specific to Kīpuka Puaulu. The companion R package, tessellr, gathers the same ideas in general form, for any points and any area. Its canopy function reads the Meta and World Resources Institute map for any frame, not only the one tile that covers the kīpuka, and its counting function adds Poisson tests and a choice of how to handle uncertain locations. The package’s tests reproduce the numbers in Chapters 3 to 9.

Install it from GitHub:

## install the companion package
remotes::install_github("kimbridges/tessellr")

Its functions, in the same five groups as the chapters:

Show the code
## the functions in tessellr, with their arguments and defaults
tessellr_functions <- read_csv("data/appendix_tessellr.csv")

## show them, grouped by stage
tessellr_functions |>
  select(stage, usage, purpose) |>
  gt(groupname_col = "stage") |>
  cols_label(usage = "Function", purpose = "Purpose") |>
  tab_style(style = cell_text(font = "monospace", size = "small"), locations = cells_body(columns = usage)) |>
  tab_style(style = cell_text(weight = "bold"), locations = cells_row_groups()) |>
  tab_source_note(source_note = "Each function has a help page with a worked example; see ?tile_points and so on once the package is loaded.")
Function Purpose
Tiles
tile_points(points, frame = NULL, clip = NULL, clip_mode = c("all", "inside"), expand = 0.05) Build Voronoi tiles around points
tile_measures(tiles, frame = NULL) Measure each tile
tile_pattern(tiles, frame = NULL, trials = 20, seed = NULL) Read the pattern of the points from their tiles
merge_tiles(tiles, by) Merge neighboring tiles that share a category
boundary_bands(tiles, by, frame = NULL) Uncertainty bands around the boundaries between categories
Containers
count_in_tiles(tiles, records, frame = NULL, uncertainty = NULL, allocation = c("exclude", "proportional"), by = NULL, test = TRUE, p_adjust = "holm", alpha = 0.05) Count records inside tiles
Weights
weighted_areas(sites, weights, frame, cell = 500) Weighted service areas
Canopy
get_canopy_height(frame, resolution = 5, pad = 50) Get canopy height for any area
structure_classes(canopy, breaks = c(2, 5, 10), labels = c("open", "low", "woodland", "forest"), smooth = 25, mmu_ha = 2) Classes of canopy structure
trace_patch(canopy, seed, height = 8, smooth = 25, soften = 20, fill_holes = TRUE) Trace the outline of a patch of tall canopy
constrained_voronoi(points, type, classes, constrained = TRUE, point_class = NULL, mmu_ha = 2) Plain or structure-constrained Voronoi map on a grid
Checking
map_agreement(map, reference, tolerance = 50) Agreement between a map and a reference map
Data
kipuka_frame() The Kīpuka Puaulu study area
kipuka_releves Twelve demonstration points at Kīpuka Puaulu
Each function has a help page with a worked example; see ?tile_points and so on once the package is loaded.

The two functions at the heart of the method

Two functions carry the main ideas. The first builds a plain Voronoi map from points, in three steps. It’s the one Chapters 3 to 6 use.

## a function for the three tile-building steps from Chapter 2
make_tiles <- function(points, frame) {
  ## build the tiles around all the points
  tiles_all <- st_voronoi(st_union(points), envelope = frame)
  ## split the result into separate polygons
  tiles <- st_collection_extract(tiles_all)
  ## trim the tiles to the frame
  tiles <- st_intersection(tiles, frame)
  ## attach each tile to the point inside it
  tiles <- st_sf(geometry = tiles)
  st_join(tiles, points)
}

The second works on a grid of cells instead of polygons. With constrained = FALSE it gives the same map as make_tiles(), drawn in cells. With constrained = TRUE it applies the rule from Chapter 8: each cell takes the type of the nearest point in the same structure class. It expects two objects to exist already: cell_xy, the coordinates of the cell centers, and cell_class, the structure class of each cell.

## a function that assigns every cell to its nearest point, with or without the structure rule
nearest_type <- function(px, py, type, point_class, constrained) {
  best <- rep(Inf, nrow(cell_xy))
  out  <- rep(NA_integer_, nrow(cell_xy))
  for (i in seq_along(px)) {
    ## squared distance from every cell to point i
    d <- ((cell_xy[, 1] - px[i])^2) + ((cell_xy[, 2] - py[i])^2)
    ## under the structure rule, points in other classes are out of reach
    if (constrained) d[cell_class != point_class[i]] <- Inf
    ## keep point i wherever it's nearer than anything found so far
    closer <- d < best
    best[closer] <- d[closer]
    out[closer]  <- type[i]
  }
  out
}

Software

The document was built with R (R Core Team 2024) and Quarto. The spatial work uses sf (Pebesma and Bivand 2023) for points and polygons and terra (Hijmans 2025) for grids. The census data for Chapter 7 came through tidycensus (Walker and Herman 2025) and tigris (Walker 2025). The versions used for this build are below.

Show the code
## the packages used in the chapters and the data scripts
packages <- c("tidyverse", "gt", "patchwork", "ggrepel", "sf", "terra", "tidycensus", "tigris", "jsonlite", "tessellr")

## their versions, with R itself at the top
software <- tibble(software = c("R", "Quarto", packages),
                   version  = c(as.character(getRversion()),
                                as.character(quarto::quarto_version()),
                                sapply(packages, function(p) tryCatch(as.character(packageVersion(p)), error = function(e) "not installed"))))

## show it
software |>
  gt() |>
  cols_label(software = "Software", version = "Version")
Software Version
R 4.3.3
Quarto 1.8.27
tidyverse 2.0.0
gt 1.2.0
patchwork 1.3.0
ggrepel 0.9.6
sf 1.0.24
terra 1.8.29
tidycensus 1.7.3
tigris 2.2.1
jsonlite 1.9.0
tessellr 0.1.0