diff --git a/R/frs_break.R b/R/frs_break.R index dcb5d6d..76a98a3 100644 --- a/R/frs_break.R +++ b/R/frs_break.R @@ -248,7 +248,7 @@ frs_break_find <- function(conn, table, to = "working.breaks", WHERE island_length >= %d", to, table, dist, dist, dist, dist, dist, - format(threshold, scientific = FALSE), + .frs_sql_num(threshold), dist ) .frs_db_execute(conn, sql) diff --git a/R/frs_habitat_classify.R b/R/frs_habitat_classify.R new file mode 100644 index 0000000..52000a5 --- /dev/null +++ b/R/frs_habitat_classify.R @@ -0,0 +1,280 @@ +#' Classify Habitat for Multiple Species +#' +#' Classify segments in a segmented stream network for one or more +#' species. Produces a long-format output table with one row per +#' segment x species, containing accessibility and habitat type +#' booleans. +#' +#' Requires a segmented streams table (from [frs_network_segment()]) +#' with `id_segment`, gradient, channel width, and ltree columns, plus +#' a breaks table (`{streams_table}_breaks`) for accessibility checks. +#' +#' @param conn A [DBI::DBIConnection-class] object (from [frs_db_conn()]). +#' @param table Character. Schema-qualified segmented streams table +#' (from [frs_network_segment()]). +#' @param to Character. Schema-qualified output table for habitat +#' classifications (e.g. `"fresh.streams_habitat"`). +#' @param species Character vector. Species codes to classify +#' (e.g. `c("CO", "BT")`). +#' @param params Named list from [frs_params()]. Default reads from +#' bundled CSV. +#' @param params_fresh Data frame from `parameters_fresh.csv`. Default +#' reads from bundled CSV. +#' @param overwrite Logical. If `TRUE`, replace existing rows for +#' these species in the output table. Default `TRUE`. +#' @param verbose Logical. Print progress. Default `TRUE`. +#' +#' @return `conn` invisibly, for pipe chaining. +#' +#' @family habitat +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' conn <- frs_db_conn() +#' +#' # Assumes fresh.streams built by frs_network_segment() with +#' # gradient barriers labeled "gradient_15", "gradient_20", "gradient_25". +#' # See frs_network_segment() for the full setup. +#' +#' # Classify CO, BT, ST — each gets species-specific accessibility. +#' # CO (15% access) is blocked by gradient_15, gradient_20, gradient_25. +#' # BT (25% access) is only blocked by gradient_25. +#' # Result: BT has ~2x the accessible habitat of CO on the same network. +#' frs_habitat_classify(conn, +#' table = "fresh.streams", +#' to = "fresh.streams_habitat", +#' species = c("CO", "BT", "ST")) +#' +#' # Query results — one table, all species, no geometry +#' DBI::dbGetQuery(conn, " +#' SELECT species_code, +#' count(*) FILTER (WHERE accessible) as accessible, +#' count(*) FILTER (WHERE spawning) as spawning, +#' count(*) FILTER (WHERE rearing) as rearing +#' FROM fresh.streams_habitat +#' GROUP BY species_code") +#' +#' # Join geometry back for mapping — id_segment links the two tables +#' DBI::dbExecute(conn, " +#' CREATE OR REPLACE VIEW fresh.streams_co_vw AS +#' SELECT s.*, h.accessible, h.spawning, h.rearing, h.lake_rearing +#' FROM fresh.streams s +#' JOIN fresh.streams_habitat h ON s.id_segment = h.id_segment +#' WHERE h.species_code = 'CO'") +#' +#' # Re-running is safe — existing rows for these species are replaced. +#' # Run more WSGs later and both tables accumulate. +#' +#' DBI::dbDisconnect(conn) +#' } +frs_habitat_classify <- function(conn, table, to, + species, + params = NULL, + params_fresh = NULL, + overwrite = TRUE, + verbose = TRUE) { + .frs_validate_identifier(table, "streams table") + .frs_validate_identifier(to, "output table") + stopifnot(is.character(species), length(species) > 0) + + breaks_tbl <- paste0(table, "_breaks") + + # Load parameters + if (is.null(params)) { + params <- frs_params(csv = system.file("extdata", + "parameters_habitat_thresholds.csv", package = "fresh")) + } + if (is.null(params_fresh)) { + params_fresh <- utils::read.csv(system.file("extdata", + "parameters_fresh.csv", package = "fresh"), stringsAsFactors = FALSE) + } + + # Create output table if not exists + .frs_db_execute(conn, sprintf( + "CREATE TABLE IF NOT EXISTS %s ( + id_segment integer, + species_code text, + accessible boolean, + spawning boolean, + rearing boolean, + lake_rearing boolean + )", to)) + + # Delete existing rows for these species + segments (idempotent) + if (overwrite) { + for (sp in species) { + .frs_db_execute(conn, sprintf( + "DELETE FROM %s WHERE species_code = %s + AND id_segment IN (SELECT id_segment FROM %s)", + to, .frs_quote_string(sp), table)) + } + } + + # Classify each species + for (sp in species) { + t0 <- proc.time() + params_sp <- params[[sp]] + fresh_sp <- params_fresh[params_fresh$species_code == sp, ] + + if (is.null(params_sp) || nrow(fresh_sp) == 0) { + if (verbose) cat(" ", sp, ": skipped (no parameters)\n", sep = "") + next + } + + access_gradient <- fresh_sp$access_gradient_max + spawn_gradient_max <- params_sp$spawn_gradient_max + spawn_gradient_min <- if (is.null(fresh_sp$spawn_gradient_min) || + is.na(fresh_sp$spawn_gradient_min)) 0 else + fresh_sp$spawn_gradient_min + + # Build label filter for this species' access threshold + # Gradient labels like "gradient_15" block species with access <= 15% + # "blocked" (falls, dams) blocks all species + label_filter <- .frs_access_label_filter(conn, breaks_tbl, access_gradient) + + # Build accessible condition: no blocking break downstream + accessible_cond <- sprintf( + "NOT EXISTS ( + SELECT 1 FROM %s b + WHERE (%s) + AND b.blue_line_key = s.blue_line_key + AND b.downstream_route_measure <= s.downstream_route_measure + ) + AND NOT EXISTS ( + SELECT 1 FROM %s b + WHERE (%s) + AND b.blue_line_key != s.blue_line_key + AND b.wscode_ltree IS NOT NULL + AND fwa_upstream(b.wscode_ltree, b.localcode_ltree, + s.wscode_ltree, s.localcode_ltree) + )", breaks_tbl, label_filter, breaks_tbl, label_filter) + + # Spawning: accessible + gradient in range + channel width in range + spawn_cond <- sprintf("s.gradient >= %s AND s.gradient <= %s", + .frs_sql_num(spawn_gradient_min), + .frs_sql_num(spawn_gradient_max)) + if (!is.null(params_sp$ranges$spawn$channel_width)) { + cw <- params_sp$ranges$spawn$channel_width + spawn_cond <- paste0(spawn_cond, sprintf( + " AND s.channel_width >= %s AND s.channel_width <= %s", + .frs_sql_num(cw[1]), + .frs_sql_num(cw[2]))) + } + + # Rearing: accessible + gradient/channel width in range + rear_cond <- "FALSE" + if (!is.null(params_sp$ranges$rear)) { + parts <- character(0) + if (!is.null(params_sp$ranges$rear$gradient)) { + g <- params_sp$ranges$rear$gradient + parts <- c(parts, sprintf("s.gradient <= %s", + .frs_sql_num(g[2]))) + } + if (!is.null(params_sp$ranges$rear$channel_width)) { + cw <- params_sp$ranges$rear$channel_width + parts <- c(parts, sprintf( + "s.channel_width >= %s AND s.channel_width <= %s", + .frs_sql_num(cw[1]), + .frs_sql_num(cw[2]))) + } + if (length(parts) > 0) rear_cond <- paste(parts, collapse = " AND ") + } + + # Lake rearing + lake_rear_cond <- "FALSE" + if (!is.null(params_sp$ranges$rear$channel_width)) { + cw <- params_sp$ranges$rear$channel_width + lake_rear_cond <- sprintf( + "s.channel_width >= %s AND s.channel_width <= %s + AND s.waterbody_key IN ( + SELECT waterbody_key FROM whse_basemapping.fwa_lakes_poly)", + .frs_sql_num(cw[1]), + .frs_sql_num(cw[2])) + } + + # Single INSERT with all classifications computed inline + sql <- sprintf( + "INSERT INTO %s (id_segment, species_code, accessible, spawning, rearing, lake_rearing) + SELECT + s.id_segment, + %s, + (%s) AS accessible, + CASE WHEN (%s) AND (%s) THEN TRUE ELSE FALSE END, + CASE WHEN (%s) AND (%s) THEN TRUE ELSE FALSE END, + CASE WHEN (%s) AND (%s) THEN TRUE ELSE FALSE END + FROM %s s", + to, + .frs_quote_string(sp), + accessible_cond, + accessible_cond, spawn_cond, + accessible_cond, rear_cond, + accessible_cond, lake_rear_cond, + table) + + .frs_db_execute(conn, sql) + + if (verbose) { + elapsed <- round((proc.time() - t0)["elapsed"], 1) + stats <- DBI::dbGetQuery(conn, sprintf( + "SELECT count(*)::int AS total, + count(*) FILTER (WHERE accessible)::int AS acc, + count(*) FILTER (WHERE spawning)::int AS spn, + count(*) FILTER (WHERE rearing)::int AS rr + FROM %s WHERE species_code = %s", + to, .frs_quote_string(sp))) + cat(" ", sp, ": ", elapsed, "s (", + stats$acc, " accessible, ", + stats$spn, " spawning, ", + stats$rr, " rearing)\n", sep = "") + } + } + + .frs_index_working(conn, to) + + invisible(conn) +} + + +#' Build SQL label filter for species-specific accessibility +#' +#' Determines which break labels block a species based on its access +#' gradient threshold. Gradient labels like `"gradient_15"` are parsed +#' to extract the threshold value. Labels >= the species' threshold +#' block that species. Non-gradient labels (e.g. `"blocked"`) always +#' block. +#' +#' @param conn DBI connection. +#' @param breaks_tbl Breaks table name. +#' @param access_gradient Numeric. Species access gradient max. +#' @return SQL predicate string for filtering breaks. +#' @noRd +.frs_access_label_filter <- function(conn, breaks_tbl, access_gradient) { + # Get distinct labels from breaks table + labels <- DBI::dbGetQuery(conn, sprintf( + "SELECT DISTINCT label FROM %s", breaks_tbl))$label + + # Determine which labels block this species + blocking <- vapply(labels, function(lbl) { + if (is.na(lbl)) return(FALSE) + # Parse gradient labels: "gradient_15" → 0.15 + m <- regmatches(lbl, regexec("^gradient_(\\d+)$", lbl))[[1]] + if (length(m) == 2) { + grad_pct <- as.numeric(m[2]) / 100 + return(grad_pct >= access_gradient) + } + # Non-gradient labels (blocked, potential, etc.) always block + TRUE + }, logical(1)) + + blocking_labels <- labels[blocking] + + if (length(blocking_labels) == 0) { + return("FALSE") # nothing blocks — all accessible + } + + quoted <- paste(vapply(blocking_labels, .frs_quote_string, character(1)), + collapse = ", ") + sprintf("b.label IN (%s)", quoted) +} diff --git a/R/frs_network_segment.R b/R/frs_network_segment.R new file mode 100644 index 0000000..70c5400 --- /dev/null +++ b/R/frs_network_segment.R @@ -0,0 +1,193 @@ +#' Segment a Stream Network at Break Points +#' +#' Build a segmented stream network by extracting base streams, enriching +#' with channel width, and splitting at break points from any number of +#' sources. Assigns a unique `id_segment` to each sub-segment. +#' +#' This function is domain-agnostic — it segments a network at points +#' without knowing what those points represent. Use [frs_break_find()] +#' to generate gradient barriers, then pass the result as a break source +#' alongside falls, crossings, or any other point table. +#' +#' @param conn A [DBI::DBIConnection-class] object (from [frs_db_conn()]). +#' @param aoi AOI specification passed to [frs_extract()]. Character +#' watershed group code, `sf` polygon, or `NULL`. +#' @param to Character. Schema-qualified output table name +#' (e.g. `"fresh.streams"`). +#' @param source Character. Source table for the stream network. Default +#' `"whse_basemapping.fwa_stream_networks_sp"`. +#' @param break_sources List of break source specs, or `NULL` (no +#' breaking). Each spec is a list with `table`, and optionally `where`, +#' `label`, `label_col`, `label_map`, `col_blk`, `col_measure`. +#' See [frs_break_find()] for details. +#' @param overwrite Logical. If `TRUE`, drop `to` before creating. +#' Default `TRUE`. +#' @param verbose Logical. Print progress. Default `TRUE`. +#' +#' @return `conn` invisibly, for pipe chaining. +#' +#' @family habitat +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' conn <- frs_db_conn() +#' +#' # --- Full workflow: barriers → segment → classify --- +#' # +#' # Species codes: CO = Coho, CH = Chinook, SK = Sockeye, +#' # ST = Steelhead, BT = Bull Trout, RB = Rainbow Trout +#' +#' # 1. Generate gradient access barriers at each species threshold. +#' # CO/CH/SK can't pass 15%, ST can't pass 20%, BT/RB can't pass 25%. +#' # Thresholds come from parameters_fresh.csv (access_gradient_max). +#' # frs_break_find needs an extracted network table to get BLK list. +#' frs_extract(conn, +#' from = "whse_basemapping.fwa_stream_networks_sp", +#' to = "working.tmp_bulk", +#' where = "watershed_group_code = 'BULK'") +#' +#' frs_break_find(conn, "working.tmp_bulk", +#' attribute = "gradient", threshold = 0.15, +#' to = "working.barriers_15") +#' frs_break_find(conn, "working.tmp_bulk", +#' attribute = "gradient", threshold = 0.20, +#' to = "working.barriers_20") +#' frs_break_find(conn, "working.tmp_bulk", +#' attribute = "gradient", threshold = 0.25, +#' to = "working.barriers_25") +#' +#' # 2. Segment the network at ALL barrier points + falls. +#' # One table, one copy of geometry, shared across all species. +#' # Labels control which species each barrier blocks — gradient_15 +#' # blocks CO but not BT; gradient_25 blocks both. +#' # Falls (from inst/extdata/falls.csv, loaded to working.falls) +#' # block all species. +#' frs_network_segment(conn, aoi = "BULK", +#' to = "fresh.streams", +#' break_sources = list( +#' list(table = "working.barriers_15", label = "gradient_15"), +#' list(table = "working.barriers_20", label = "gradient_20"), +#' list(table = "working.barriers_25", label = "gradient_25"), +#' list(table = "working.falls", +#' where = "barrier_ind = TRUE", label = "blocked") +#' )) +#' +#' # 3. Classify habitat — see frs_habitat_classify() for details. +#' # Writes to fresh.streams_habitat (long format, no geometry). +#' # id_segment links back to fresh.streams for mapping. +#' frs_habitat_classify(conn, +#' table = "fresh.streams", +#' to = "fresh.streams_habitat", +#' species = c("CO", "BT", "ST")) +#' +#' # Check results +#' DBI::dbGetQuery(conn, " +#' SELECT species_code, +#' count(*) FILTER (WHERE accessible) as accessible, +#' count(*) FILTER (WHERE spawning) as spawning +#' FROM fresh.streams_habitat +#' GROUP BY species_code") +#' +#' DBI::dbDisconnect(conn) +#' } +frs_network_segment <- function(conn, aoi, to, + source = "whse_basemapping.fwa_stream_networks_sp", + break_sources = NULL, + overwrite = TRUE, + verbose = TRUE) { + .frs_validate_identifier(to, "output table") + + t0 <- proc.time() + + # -- Extract base network -------------------------------------------------- + if (overwrite) { + .frs_db_execute(conn, sprintf("DROP TABLE IF EXISTS %s", to)) + } + + if (is.character(aoi) && length(aoi) == 1 && grepl("^[A-Z]{4}$", aoi)) { + frs_extract(conn, from = source, to = to, + where = paste0("watershed_group_code = ", .frs_quote_string(aoi)), + overwrite = FALSE) + } else { + frs_extract(conn, from = source, to = to, + aoi = aoi, overwrite = FALSE) + } + + if (verbose) { + n <- DBI::dbGetQuery(conn, + sprintf("SELECT count(*)::int AS n FROM %s", to))$n + cat(" Base: ", n, " segments (", + round((proc.time() - t0)["elapsed"], 1), "s)\n", sep = "") + } + + # -- Enrich with channel width --------------------------------------------- + frs_col_join(conn, to, + from = "fwa_stream_networks_channel_width", + cols = c("channel_width", "channel_width_source"), + by = "linear_feature_id") + + # -- Add id_segment -------------------------------------------------------- + .frs_add_id_segment(conn, to) + + # -- Apply break sources --------------------------------------------------- + if (!is.null(break_sources) && length(break_sources) > 0) { + breaks_tbl <- paste0(to, "_breaks") + + for (i in seq_along(break_sources)) { + src <- break_sources[[i]] + .frs_validate_identifier(src$table, "break source table") + t1 <- proc.time() + + frs_break_find(conn, to, + points_table = src$table, + where = src$where, + label = src$label, + label_col = src$label_col, + label_map = src$label_map, + col_blk = if (is.null(src$col_blk)) "blue_line_key" else src$col_blk, + col_measure = if (is.null(src$col_measure)) "downstream_route_measure" else src$col_measure, + to = breaks_tbl, + overwrite = (i == 1), append = (i > 1)) + + if (verbose) { + n_brk <- DBI::dbGetQuery(conn, + sprintf("SELECT count(*)::int AS n FROM %s WHERE source = %s", + breaks_tbl, .frs_quote_string(src$table)))$n + cat(" Breaks from ", src$table, ": ", n_brk, " (", + round((proc.time() - t1)["elapsed"], 1), "s)\n", sep = "") + } + } + + # Enrich breaks with ltree for classify + .frs_enrich_breaks(conn, breaks_tbl) + + # Apply breaks to geometry + t1 <- proc.time() + frs_break_apply(conn, to, breaks = breaks_tbl, segment_id = "id_segment") + + if (verbose) { + n <- DBI::dbGetQuery(conn, + sprintf("SELECT count(*)::int AS n FROM %s", to))$n + cat(" Segmented: ", n, " segments (", + round((proc.time() - t1)["elapsed"], 1), "s)\n", sep = "") + } + + # Recompute gradient/measures from new geometry + frs_col_generate(conn, to) + + # Keep breaks table for frs_habitat_classify (accessibility check) + # Caller or frs_habitat can clean up later + } + + # -- Index ----------------------------------------------------------------- + .frs_index_working(conn, to) + + if (verbose) { + total <- round((proc.time() - t0)["elapsed"], 1) + cat(" Total: ", total, "s\n", sep = "") + } + + invisible(conn) +} diff --git a/R/utils.R b/R/utils.R index af2734a..824217e 100644 --- a/R/utils.R +++ b/R/utils.R @@ -60,6 +60,19 @@ } +#' Format a numeric value as a locale-safe SQL literal +#' +#' Uses `sprintf` which is not affected by `options(OutDec)`, +#' unlike `format()` or `formatC()`. +#' +#' @param x Numeric scalar. +#' @return Character string safe for SQL interpolation. +#' @noRd +.frs_sql_num <- function(x) { + sprintf("%.10g", x) +} + + #' Add id_segment column to a working table #' #' Assigns a unique integer ID to every row. Uses `linear_feature_id` @@ -133,6 +146,15 @@ if ("watershed_group_code" %in% cols) { idx <- c(idx, sprintf("CREATE INDEX ON %s (watershed_group_code)", table)) } + if ("label" %in% cols) { + idx <- c(idx, sprintf("CREATE INDEX ON %s (label)", table)) + } + if ("id_segment" %in% cols) { + idx <- c(idx, sprintf("CREATE INDEX ON %s (id_segment)", table)) + } + if ("species_code" %in% cols) { + idx <- c(idx, sprintf("CREATE INDEX ON %s (species_code)", table)) + } for (sql in idx) { .frs_db_execute(conn, sql) diff --git a/man/frs_habitat_classify.Rd b/man/frs_habitat_classify.Rd new file mode 100644 index 0000000..546790f --- /dev/null +++ b/man/frs_habitat_classify.Rd @@ -0,0 +1,113 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/frs_habitat_classify.R +\name{frs_habitat_classify} +\alias{frs_habitat_classify} +\title{Classify Habitat for Multiple Species} +\usage{ +frs_habitat_classify( + conn, + table, + to, + species, + params = NULL, + params_fresh = NULL, + overwrite = TRUE, + verbose = TRUE +) +} +\arguments{ +\item{conn}{A \link[DBI:DBIConnection-class]{DBI::DBIConnection} object (from \code{\link[=frs_db_conn]{frs_db_conn()}}).} + +\item{table}{Character. Schema-qualified segmented streams table +(from \code{\link[=frs_network_segment]{frs_network_segment()}}).} + +\item{to}{Character. Schema-qualified output table for habitat +classifications (e.g. \code{"fresh.streams_habitat"}).} + +\item{species}{Character vector. Species codes to classify +(e.g. \code{c("CO", "BT")}).} + +\item{params}{Named list from \code{\link[=frs_params]{frs_params()}}. Default reads from +bundled CSV.} + +\item{params_fresh}{Data frame from \code{parameters_fresh.csv}. Default +reads from bundled CSV.} + +\item{overwrite}{Logical. If \code{TRUE}, replace existing rows for +these species in the output table. Default \code{TRUE}.} + +\item{verbose}{Logical. Print progress. Default \code{TRUE}.} +} +\value{ +\code{conn} invisibly, for pipe chaining. +} +\description{ +Classify segments in a segmented stream network for one or more +species. Produces a long-format output table with one row per +segment x species, containing accessibility and habitat type +booleans. +} +\details{ +Requires a segmented streams table (from \code{\link[=frs_network_segment]{frs_network_segment()}}) +with \code{id_segment}, gradient, channel width, and ltree columns, plus +a breaks table (\verb{\{streams_table\}_breaks}) for accessibility checks. +} +\examples{ +\dontrun{ +conn <- frs_db_conn() + +# Assumes fresh.streams built by frs_network_segment() with +# gradient barriers labeled "gradient_15", "gradient_20", "gradient_25". +# See frs_network_segment() for the full setup. + +# Classify CO, BT, ST — each gets species-specific accessibility. +# CO (15\% access) is blocked by gradient_15, gradient_20, gradient_25. +# BT (25\% access) is only blocked by gradient_25. +# Result: BT has ~2x the accessible habitat of CO on the same network. +frs_habitat_classify(conn, + table = "fresh.streams", + to = "fresh.streams_habitat", + species = c("CO", "BT", "ST")) + +# Query results — one table, all species, no geometry +DBI::dbGetQuery(conn, " + SELECT species_code, + count(*) FILTER (WHERE accessible) as accessible, + count(*) FILTER (WHERE spawning) as spawning, + count(*) FILTER (WHERE rearing) as rearing + FROM fresh.streams_habitat + GROUP BY species_code") + +# Join geometry back for mapping — id_segment links the two tables +DBI::dbExecute(conn, " + CREATE OR REPLACE VIEW fresh.streams_co_vw AS + SELECT s.*, h.accessible, h.spawning, h.rearing, h.lake_rearing + FROM fresh.streams s + JOIN fresh.streams_habitat h ON s.id_segment = h.id_segment + WHERE h.species_code = 'CO'") + +# Re-running is safe — existing rows for these species are replaced. +# Run more WSGs later and both tables accumulate. + +DBI::dbDisconnect(conn) +} +} +\seealso{ +Other habitat: +\code{\link{frs_aggregate}()}, +\code{\link{frs_break}()}, +\code{\link{frs_break_apply}()}, +\code{\link{frs_break_find}()}, +\code{\link{frs_break_validate}()}, +\code{\link{frs_categorize}()}, +\code{\link{frs_classify}()}, +\code{\link{frs_col_generate}()}, +\code{\link{frs_col_join}()}, +\code{\link{frs_extract}()}, +\code{\link{frs_habitat}()}, +\code{\link{frs_habitat_access}()}, +\code{\link{frs_habitat_partition}()}, +\code{\link{frs_habitat_species}()}, +\code{\link{frs_network_segment}()} +} +\concept{habitat} diff --git a/man/frs_network_segment.Rd b/man/frs_network_segment.Rd new file mode 100644 index 0000000..d042247 --- /dev/null +++ b/man/frs_network_segment.Rd @@ -0,0 +1,134 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/frs_network_segment.R +\name{frs_network_segment} +\alias{frs_network_segment} +\title{Segment a Stream Network at Break Points} +\usage{ +frs_network_segment( + conn, + aoi, + to, + source = "whse_basemapping.fwa_stream_networks_sp", + break_sources = NULL, + overwrite = TRUE, + verbose = TRUE +) +} +\arguments{ +\item{conn}{A \link[DBI:DBIConnection-class]{DBI::DBIConnection} object (from \code{\link[=frs_db_conn]{frs_db_conn()}}).} + +\item{aoi}{AOI specification passed to \code{\link[=frs_extract]{frs_extract()}}. Character +watershed group code, \code{sf} polygon, or \code{NULL}.} + +\item{to}{Character. Schema-qualified output table name +(e.g. \code{"fresh.streams"}).} + +\item{source}{Character. Source table for the stream network. Default +\code{"whse_basemapping.fwa_stream_networks_sp"}.} + +\item{break_sources}{List of break source specs, or \code{NULL} (no +breaking). Each spec is a list with \code{table}, and optionally \code{where}, +\code{label}, \code{label_col}, \code{label_map}, \code{col_blk}, \code{col_measure}. +See \code{\link[=frs_break_find]{frs_break_find()}} for details.} + +\item{overwrite}{Logical. If \code{TRUE}, drop \code{to} before creating. +Default \code{TRUE}.} + +\item{verbose}{Logical. Print progress. Default \code{TRUE}.} +} +\value{ +\code{conn} invisibly, for pipe chaining. +} +\description{ +Build a segmented stream network by extracting base streams, enriching +with channel width, and splitting at break points from any number of +sources. Assigns a unique \code{id_segment} to each sub-segment. +} +\details{ +This function is domain-agnostic — it segments a network at points +without knowing what those points represent. Use \code{\link[=frs_break_find]{frs_break_find()}} +to generate gradient barriers, then pass the result as a break source +alongside falls, crossings, or any other point table. +} +\examples{ +\dontrun{ +conn <- frs_db_conn() + +# --- Full workflow: barriers → segment → classify --- +# +# Species codes: CO = Coho, CH = Chinook, SK = Sockeye, +# ST = Steelhead, BT = Bull Trout, RB = Rainbow Trout + +# 1. Generate gradient access barriers at each species threshold. +# CO/CH/SK can't pass 15\%, ST can't pass 20\%, BT/RB can't pass 25\%. +# Thresholds come from parameters_fresh.csv (access_gradient_max). +# frs_break_find needs an extracted network table to get BLK list. +frs_extract(conn, + from = "whse_basemapping.fwa_stream_networks_sp", + to = "working.tmp_bulk", + where = "watershed_group_code = 'BULK'") + +frs_break_find(conn, "working.tmp_bulk", + attribute = "gradient", threshold = 0.15, + to = "working.barriers_15") +frs_break_find(conn, "working.tmp_bulk", + attribute = "gradient", threshold = 0.20, + to = "working.barriers_20") +frs_break_find(conn, "working.tmp_bulk", + attribute = "gradient", threshold = 0.25, + to = "working.barriers_25") + +# 2. Segment the network at ALL barrier points + falls. +# One table, one copy of geometry, shared across all species. +# Labels control which species each barrier blocks — gradient_15 +# blocks CO but not BT; gradient_25 blocks both. +# Falls (from inst/extdata/falls.csv, loaded to working.falls) +# block all species. +frs_network_segment(conn, aoi = "BULK", + to = "fresh.streams", + break_sources = list( + list(table = "working.barriers_15", label = "gradient_15"), + list(table = "working.barriers_20", label = "gradient_20"), + list(table = "working.barriers_25", label = "gradient_25"), + list(table = "working.falls", + where = "barrier_ind = TRUE", label = "blocked") + )) + +# 3. Classify habitat — see frs_habitat_classify() for details. +# Writes to fresh.streams_habitat (long format, no geometry). +# id_segment links back to fresh.streams for mapping. +frs_habitat_classify(conn, + table = "fresh.streams", + to = "fresh.streams_habitat", + species = c("CO", "BT", "ST")) + +# Check results +DBI::dbGetQuery(conn, " + SELECT species_code, + count(*) FILTER (WHERE accessible) as accessible, + count(*) FILTER (WHERE spawning) as spawning + FROM fresh.streams_habitat + GROUP BY species_code") + +DBI::dbDisconnect(conn) +} +} +\seealso{ +Other habitat: +\code{\link{frs_aggregate}()}, +\code{\link{frs_break}()}, +\code{\link{frs_break_apply}()}, +\code{\link{frs_break_find}()}, +\code{\link{frs_break_validate}()}, +\code{\link{frs_categorize}()}, +\code{\link{frs_classify}()}, +\code{\link{frs_col_generate}()}, +\code{\link{frs_col_join}()}, +\code{\link{frs_extract}()}, +\code{\link{frs_habitat}()}, +\code{\link{frs_habitat_access}()}, +\code{\link{frs_habitat_classify}()}, +\code{\link{frs_habitat_partition}()}, +\code{\link{frs_habitat_species}()} +} +\concept{habitat}