Ingest NMFS ESA Critical Habitat → global 0.05° cells
Rasterize each species’ NMFS ESA critical-habitat polygon(s) onto the global 0.05° cell grid and write one Parquet per species to the marine-atlas release, keyed by mdl_key = "ch_nmfs|{sp_id}" (sp_id = a slug of the scientific name — NMFS critical habitat has no numeric species id).
Method. msens::cells_from_ranges() (exactextractr) marks every cell the (unioned) polygon overlaps, capturing the whole footprint with no land mask (here ocean anyway, critical habitat being marine). The val is the species’ extinction-risk score via msens::compute_er_score() — never hard-coded — from its most-protective ESA status (NMFS:EN = 100, NMFS:TN = 50). This is the shared vector → cells pattern reused by every range dataset (BOTW, FWS, NMFS core areas, turtles).
Source ply.gpkg is derived from the NMFS ESA Critical Habitat geodatabase (see old/ingest_fisheries.noaa.gov_critical-habitat_v7.qmd for that one-time extraction + QA maps). Re-run control: REDO_INGEST=1 rebuilds all models (else the loop resumes, skipping species whose Parquet exists).
1 Design
2 Setup
Code
librarian::shelf(
arrow, DBI, dplyr, duckdb, fs, glue, here, jsonlite, logger, readr, sf, terra,
tibble, MarineSensitivity/msens, quiet = T)
source(here("libs/paths.R")) # dir_raw, dir_big_v, cellid_tif, ver, s3_atlas
source(here("libs/vars.R")) # redo_ingest
options(readr.show_col_types = F)
sf_use_s2(FALSE) # critical-habitat polygons are planar-valid, s2-invalid (self-Spherical geometry (s2) switched off
Code
# crossing loops); planar GEOS unions/valids them (matches ch_fws/rng_fws)
ds_key <- "ch_nmfs"
ply_geo <- glue("{dir_raw}/fisheries.noaa.gov/ply.gpkg") # per-species critical-habitat polygons
dir_dist <- glue("{dir_big_v}/marine-atlas/dist/dataset={ds_key}")
manifest <- here("data/manifests/ingest_ch_nmfs.json")
dir_create(c(dir_dist, path_dir(manifest)))
stopifnot(
"run build_cell_grid.qmd first (need cellid_tif)" = file_exists(cellid_tif),
"ply.gpkg missing (derive it, see old/..._v7.qmd)" = file_exists(ply_geo))3 Read polygons + per-species model crosswalk
One model per species (SCIENAME); its multiple critical-habitat polygons are unioned at rasterize time.
Code
ply <- st_read(ply_geo, quiet = TRUE)
sp <- ply |>
st_drop_geometry() |>
group_by(SCIENAME) |> # one model per species (polygons unioned below)
summarise(
TAXON = first(TAXON), COMNAME = first(COMNAME),
# most-protective ESA status among the species' critical-habitat features
status = if_else(any(grepl("Endangered", LISTSTATUS)), "EN", "TN"), .groups = "drop") |>
arrange(SCIENAME) |>
mutate(
sp_id = gsub("^_|_$", "", gsub("[^A-Za-z0-9]+", "_", SCIENAME)),
mdl_key = mdl_key_raw(ds_key, sp_id),
er_code = paste0("NMFS:", status),
er_score = compute_er_score(er_code)) # val via compute_er_score(), never hard-coded
stopifnot(!any(duplicated(sp$sp_id)))
write_csv(sp, glue("{dir_dist}/../model_{ds_key}.csv"))
nrow(sp)[1] 38
4 Rasterize each range → one Parquet each
Code
if (redo_ingest && dir_exists(dir_dist)) { dir_delete(dir_dist); dir_create(dir_dist) }
done <- path_ext_remove(path_file(dir_ls(dir_dist, glob = "*.parquet")))
todo <- sp |> filter(!sp_id %in% done)
log_info("{ds_key}: {length(done)} done; rasterizing {nrow(todo)} of {nrow(sp)} ranges ...")
for (i in seq_len(nrow(todo))) {
row <- todo[i, ]
ply_sp <- ply |> filter(SCIENAME == row$SCIENAME) |> summarise(.groups = "drop")
d <- cells_from_ranges(ply_sp, cellid_tif, value = row$er_score) # ER score, not hard-coded
if (nrow(d) == 0) { log_warn(" {row$sp_id}: no ocean cells covered"); next }
msens::write_atlas_parquet(
tibble(mdl_key = row$mdl_key, cell_id = d$cell_id, val = d$val),
path(dir_dist, row$sp_id, ext = "parquet"))
}although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
although coordinates are longitude/latitude, st_union assumes that they are
planar
Code
log_info("{ds_key}: done")5 Verify
Code
pq <- dir_ls(dir_dist, glob = "*.parquet")
con <- dbConnect(duckdb())
smry <- dbGetQuery(con, glue(
"SELECT count(DISTINCT mdl_key) n_models, count(*) n_cells,
min(val) v_min, max(val) v_max
FROM read_parquet('{dir_dist}/*.parquet')"))
# order-independent content fingerprint of the on-disk surface (not the ingest run)
h <- msens::hash_parquet(glue("{dir_dist}/*.parquet"), con)
dbDisconnect(con, shutdown = TRUE)
msens::report_table(smry, caption = "ch_nmfs model_cell surface")| n_models | n_cells | v_min | v_max |
|---|---|---|---|
| 38 | 267897 | 50 | 100 |
6 Manifest (target output)
Code
# content-addressed manifest: deterministic, no wall-clock, no machine paths ->
# downstream targets re-run only when this surface's content actually changes
msens::write_manifest(
manifest, target = "ingest_ch_nmfs", content_hash = h,
stats = list(ds_key = ds_key, ver = ver, n_models = nrow(sp),
n_cells = smry$n_cells, v_min = smry$v_min, v_max = smry$v_max,
n_parquet = length(pq), source = path_file(ply_geo)),
force = msens::force_target("ingest_ch_nmfs"))