Skip to contents

For any stream in a watershed, fisheries managers want to know three things: can a fish get there, is it good habitat, and what — if anything — blocks the way. This vignette runs that analysis end to end for the Parsnip River Watershed Group (PARS, ~5,600 km², north-eastern BC).

link models the entire stream network of a watershed group one segment at a time, and for each species works out:

  • Access — can the fish reach the segment, or is the route downstream likely too steep (gradient), or cut off by a barrier such as a dam, waterfall, or perched culvert?
  • Habitat — if it is reachable, is the segment likely large enough (channel width) and its gradient gentle enough for the modelled species to use it as spawning habitat, rearing habitat, or simply passable water with neither?
  • Barriers — the most significant obstacle downstream: a dam, a known barrier that has been assessed in the field (a road culvert, weir, etc.), a modelled crossing (predicted from road–stream intersections but not yet field-checked), or one that has since been remediated (fixed).

It also flags reaches that are intermittent (seasonally dry). bcfishpass condenses that whole per-segment verdict into one compact label it calls a mapping_code — for example SPAWN;DAM, spawning habitat with a dam downstream. link reproduces those same labels, and the maps below colour and weight every stream by them.

The vignette does two things. First, it shows link’s bcfishpass configuration reproducing bcfishpass’s per-segment classification for bull trout (BT) — a parity check against the established tool. Second, it shows link extending the same method to a species bcfishpass does not yet model in the Peace: Arctic grayling (GR).

The Parsnip River Watershed Group sits between Prince George and Mackenzie, BC. The Parsnip flows north into the southern arm of Williston Reservoir, joining the Peace River system; from there the drainage runs Peace → Slave → Mackenzie, ultimately discharging to the Arctic Ocean via the Mackenzie Delta. Of the species in link’s bcfishpass configuration, bull trout (BT) is the only one present, so the parity check below is a single-species comparison. Both bull trout and Arctic grayling (GR) are cold-water species whose distributions the model resolves through gradient, channel-width, and access thresholds. Bull trout — provincially blue-listed and a COSEWIC species of special concern in the Western Arctic population — spawn as adfluvial migrants in cold, low-gradient tributaries such as the Misinchinka and Anzac. Arctic grayling, at the southern edge of their range in the Williston watershed, hold to cooler, larger (fourth-order and up) clear-water reaches and spawn over fine gravels.

link is layered on the canonical fwapg / bcfishpass / bcfishobs stack from Simon Norris (Hillcrest Geographics). What link adds is a configuration-driven re-expression of the same modelling: we can experiment with different configurations for species bcfishpass already models, and extend to species it does not — here, Arctic grayling — while staying byte-checkable against the upstream reference.

Modelling parameters

A mapping_code is a per-segment, per-species label that bcfishpass computes over the BC Freshwater Atlas (FWA) stream network — it is not part of the FWA itself. Producing it means first re-cutting the FWA streams into shorter segments wherever a fish’s prospects change — at gradient transitions, falls, dams, modelled and assessed crossings, and habitat thresholds — then giving each resulting segment its access, spawning, and rearing classification. link reproduces that segmentation and those classifications. Access is gated by a per-species maximum gradient; spawning and rearing are then gated by their own maximum gradients and a minimum channel width. The values in force for this run are below.

Access, spawning, and rearing gradient ceilings and minimum channel widths for the two species mapped in this vignette. Bull trout is modelled by both bcfishpass and link’s bcfishpass config (the parity case); Arctic grayling is modelled only by link’s default config (the extension). Grayling’s lower access ceiling (0.15 vs 0.25) and narrower spawning/rearing gradient windows are what give it the smaller modelled network seen below.
species configuration access grad max spawn grad max rear grad max spawn CW min (m) rear CW min (m)
BT bcfishpass (parity) 0.25 0.0549 0.1049 2 1.5
GR default (link extension) 0.15 0.0249 0.0349 4 1.5

Cached inputs

The model run and the bcfishpass comparison both require a populated PostgreSQL/PostGIS database and a bcfishpass snapshot — neither of which exists on the documentation-build CI. So they run once, locally, in data-raw/wsg_vignette_data.R (generic — set aoi and re-run for any watershed group), which caches its outputs to inst/vignette-data/. This vignette only loads those artifacts; it never touches a database at build time. Direct downloads from the repo (open in QGIS or any GDAL-aware tool):

  • pars.gpkg — vectors: aoi (WSG boundary), streams (per-segment mapping_code_bt from the bcfishpass config + mapping_code_gr from the default config), waterbodies (lakes + rivers + manmade), named_streams, and basemapping context layers reserves, parks, roads, railways
  • pars_parity.rds — per-species mapping_code parity tibble
  • pars_accessible.rds — bull-trout accessible / spawning / rearing habitat (km), link’s roll-up vs the local bcfishpass snapshot

The model run itself — shown here for reference, not executed at build time — is one call per configuration. The Peace study-area run that produced the state this vignette reads modelled the drainage most-downstream-watershed-group first, so a segment’s downstream-dam (;DAM) tokens, which can live in an adjacent watershed group, resolve correctly. A standalone single-group re-run would diverge on exactly those cross-group segments, so the data-gen script reads the persisted study-area state rather than recomputing it.

conn <- lnk_db_conn()

cfg_bcfp <- lnk_config("bcfishpass")          # persists to schema `fresh`
loaded   <- lnk_load_overrides(cfg_bcfp)
lnk_pipeline_run(conn, aoi = "PARS", cfg = cfg_bcfp, loaded = loaded,
                 mapping_code = TRUE)

cfg_default <- lnk_config("default")          # persists to `fresh_default`
cfg_default$pipeline$schema <- "fresh_default"
loaded_d <- lnk_load_overrides(cfg_default)
lnk_pipeline_run(conn, aoi = "PARS", cfg = cfg_default, loaded = loaded_d,
                 mapping_code = TRUE)

Reproducing bcfishpass (parity)

lnk_compare_mapping_code() compares link’s per-segment mapping_code against the local bcfishpass snapshot, segment by segment. The comparison restricts itself to species that are actually active in the watershed group — which, for the reasons above, is bull trout alone in PARS.

Per-segment mapping_code parity for bull trout in PARS, link’s bcfishpass config vs the local bcfishpass snapshot.
WSG species segments match %
PARS BT 43600 98.91

link reproduces 98.91% of bcfishpass’s per-segment bull-trout mapping_code across 43,600 segments — consistent with the 99.66% study-area median established for the Peace.

Accessible habitat (km)

Per-segment mapping_code agreement is one lens; the habitat totals are another. lnk_rollup_wsg() sums, per species, the stream length a fish can reach (accessible) and the lengths modelled as spawning and rearing — link’s own model output, produced whether or not there is anything to compare against. Below, those totals sit next to the same quantities from the bcfishpass habitat model. Because link breaks streams at every gradient frontier, a reach never straddles the accessibility boundary, so the accessible total matches bcfishpass to a rounding error.

Bull-trout accessible / spawning / rearing habitat (km) in PARS: link’s roll-up vs the local bcfishpass snapshot. Accessible habitat matches to a rounding error; spawning and rearing agree within the habitat model’s tolerance (both well inside the 5% parity band).
metric link km bcfishpass km diff %
accessible 6822.47 6822.88 -0.01
spawning 1683.38 1667.92 0.93
rearing 2575.06 2588.91 -0.53

link models 6,822.5 km of accessible bull-trout habitat in PARS against bcfishpass’s 6,822.9 km — a -0.01% difference. Spawning and rearing agree within the habitat model’s tolerance.

Bull-trout per-segment mapping_code across the Parsnip River Watershed Group, link's bcfishpass configuration. Stream colours come straight from the bcfishpass symbology registry, so they match a bcfishpass QGIS project exactly. Context: lakes/rivers/manmade waterbodies (light blue), provincial parks (green), First Nations reserves (grey polygon + black diamond + label), resource roads (grey), railways (black dashed); the heavy black line is the watershed-group boundary.

Bull-trout per-segment mapping_code across the Parsnip River Watershed Group, link’s bcfishpass configuration. Stream colours come straight from the bcfishpass symbology registry, so they match a bcfishpass QGIS project exactly. Context: lakes/rivers/manmade waterbodies (light blue), provincial parks (green), First Nations reserves (grey polygon + black diamond + label), resource roads (grey), railways (black dashed); the heavy black line is the watershed-group boundary.

bcfishpass does not yet model Arctic grayling, so there is nothing to compare against; this is net-new output. link’s default configuration carries GR in its species dimensions, and the same six-phase pipeline that produced the bull-trout parity above produces a per-segment mapping_code for grayling. The map below is rendered with the same bcfishpass symbology registry — the token vocabulary (ACCESS/SPAWN/REAR × NONE/MODELLED/ASSESSED/DAM/…) is species-agnostic, so one colour lookup styles every species consistently.

Arctic grayling per-segment mapping_code across the Parsnip River Watershed Group, link's default configuration — a species bcfishpass does not yet model. Same symbology registry and context layers as the bull-trout map, so the two are directly comparable. The grayling network is smaller than bull trout's (19,232 vs 38,622 classified segments), but every grayling segment is net-new output relative to bcfishpass, and 257 of them carry no bull-trout classification at all.

Arctic grayling per-segment mapping_code across the Parsnip River Watershed Group, link’s default configuration — a species bcfishpass does not yet model. Same symbology registry and context layers as the bull-trout map, so the two are directly comparable. The grayling network is smaller than bull trout’s (19,232 vs 38,622 classified segments), but every grayling segment is net-new output relative to bcfishpass, and 257 of them carry no bull-trout classification at all.

Maps — detail comparison

The full-watershed views compress a lot of network. Cropping to a sub-reach puts bull trout and grayling side by side at full resolution. Grayling’s modelled network is the smaller of the two overall, but 257 segments carry a grayling classification with no bull-trout classification — the reaches where the extension is doing genuinely new work.

South-east corner of the Parsnip River Watershed Group at full resolution — the headwaters near the continental divide: bull trout (left, link bcfishpass config) and Arctic grayling (right, link default config), same extent, same symbology. Grey background streams are the full modelled network, so the coloured overlay shows where each species' classification reaches. Context: waterbodies (light blue), parks (green), reserves (grey + diamond), roads (grey), railways (black dashed), named streams (italic blue labels).

South-east corner of the Parsnip River Watershed Group at full resolution — the headwaters near the continental divide: bull trout (left, link bcfishpass config) and Arctic grayling (right, link default config), same extent, same symbology. Grey background streams are the full modelled network, so the coloured overlay shows where each species’ classification reaches. Context: waterbodies (light blue), parks (green), reserves (grey + diamond), roads (grey), railways (black dashed), named streams (italic blue labels).