HUC12 Watershed Spatial Projection
Brian S. Yandell
28 July 2026
watershed.RmdPrompt
-
Context: The
ewingpredator-prey simulation uses an abstract tridiagonal coordinate system. This needs to be spatially projected onto real-world geography utilizing the Watershed Boundary Dataset (HUC 12s). Our specific target is Isle Royale in Lake Superior (HUC12:041800000101). - Role: Expert R Developer and Spatial Integrator.
-
Action: Review prior work
(
inst/doc/refactor/triangle.md,inst/scripts/substrate_triangle.R) to execute two foundational steps:- Algebraic Refactoring: Architect a more elegant, object-oriented S3 methodology to simplify tri-coordinate math (i.e. replacing item-by-item grid manipulations).
-
Subwatershed Overlay pipeline: Develop actionable
scripts (e.g.,
inst/scripts/watershed_overlay.R) to programmatically fetch HUC12 boundaries, intersect them with specific geographic features (like Isle Royale), and overlay a scalable spatial hexagonal grid using standard bounding frameworks.
- Format: Tracked feedback and architectural outlines.
- Tone: Professional, constructive, and encouraging.
Expert Review & Proposed Architecture
This is a fantastic application of the ewing package’s
tridiagonal infrastructure. Transitioning from abstract ecological
topologies (like plant substrates) to geographic spatial datasets (like
HUC12 boundaries) heavily benefits from object-oriented refinement.
Here are the tracked changes and design propositions required to elegantly construct this overlay:
1. Refactoring Tri-Coordinate Algebra via S3 Classes
Addressing the (a, b, c) coordinate vectors item-by-item
is brittle and mathematically verbose. By wrapping coordinates into a
standardized tricoord S3 class structure in
R/triangle.R, we can implement operator overloading. This
empowers R to apply spatial topology translations natively to
data.frames and vectors containing these coordinates utilizing elegant
var1 + var2 syntax!
Feedback Tracked Details
(inst/scripts/substrate_triangle.R):
# Apply geometric offset
o_a <- cfg$offset[1]
o_b <- cfg$offset[2]
o_c <- cfg$offset[3]
- grid$a <- grid$a + o_a
- grid$b <- grid$b + o_b
- grid$c <- grid$c + o_c
+ grid <- grid + cfg$offset
# Map to Cartesian Coordinates
- car_pts <- tri2car(rbind(grid$a, grid$b, grid$c))
+ car_pts <- tri2car(grid)Explanation: We extract the raw scalar math and matrix
transpositions into the base class logic, significantly reducing visual
noise and the likelihood of matrix dimension errors (which historically
occurred with rbind/cbind
mismatches).
2. Developing Overlay Projections for HUC12 Subwatersheds
To project the tridiagonal substrate network atop HUC12 polygon data
(like Isle Royale 041800000101), we require a
transformation pipeline converting mathematical
bounds securely into the sf package’s CRS (Coordinate
Reference Systems):
[!TIP] Suggested Geographic Projection Pipeline:
- Data Acquisition & Restriction: Utilize
nhdplusToolsto fetch the base watershed boundary (get_huc). If targeting a specific geographic entity (like an island or national park), useosmdata::getbb()to download its bounds and spatially clip the HUC layer usingsf::st_intersection(), removing unrelated landmasses.- Hexagonal Mesh Generation: Calculate the restricted bounds and pass the layer to
sf::st_make_grid(square = FALSE, cellsize = c(diameter, diameter))to generate a mathematically uniform spatial hexagonal grid natively spanning the Coordinate Reference System.- Topology Filtering: Drop redundant/empty hexagons extending into the water by checking bounding box intersections via
st_intersects().- Spatial Geometry Overlays: Render both the base map constraints and the generated hexagonal mesh into one figure utilizing
ggplot2::geom_sf(), laying the groundwork for spatial agent dispersion.
3. Implementation of the Geographic Pipeline
The script inst/scripts/watershed_overlay.R implements
this advanced pipeline for the target Isle Royale HUC12. It successfully
pairs nhdplusTools::get_huc() with osmdata
named feature filtering (getbb("Isle Royale")) to isolate
the exact island landmass via spatial intersection
(st_intersection).
Crucially, it replaces the theoretical plant linkage grid with a true
spatial implementation: generating a parameterized 0.01
degree hexagonal grid across the island layout using
sf::st_make_grid(). Only segments physically touching the
island are retained, completing a scalable architectural foundation for
mapping localized continuous movement across arbitrary geographical
topologies!
4. Object-Oriented Hexagonal Overlay Refactoring
Reflecting the broader repository migration towards reusable,
programmatic components, the structural functions powering
inst/scripts/watershed_overlay.R have been fully formalized
and abstracted into the central package architecture within
R/watershed.R. Former legacy bridging code routing to the
substrate_triangle.R routines has been fully purged in
favor of strict, native spatial math:
-
get_watershed(huc_id, feature_name): Upgraded to globally preserve spatial bindings within the data, tracking and returning embedded parameter IDs back along with parsed$layer,$lon, and$lattraits for downstream processing intact. -
add_watershed_hex_overlay(huc_info, hex_diameter = 0.01): A dedicated data constructor. It mathematically generates thest_make_gridconfigurations overlaying bounding restrictions securely against dynamic hex parameters. Emits an S3 target ofclass = "watershed_hex_overlay". -
autoplot.watershed_hex_overlay(object): Translates abstract topology data automatically viaggplot2. Rendering geographically mapped interactions is now as intuitive as simply runningautoplot(hex_obj).
5. Shiny Application Interface (watershedApp.R)
We migrated the static mapping logic originally housed in
inst/scripts/watershed_overlay.R into a dedicated modular
Shiny application at R/watershedApp.R.
Following the ewing package’s UI conventions
(ewingApp.R), this modular application decoupled into four
canonical chunks:
-
watershedApp(): The macro-wrapper establishing the UI shell and bridging the server invocation. -
watershedInput(id): A generic UI controller block holding basictextInputparameters forhuc12_idand thefeature_name. Crucially, anactionButtonwas bound to explicitly submit queries, preventing rapid API polling against NHD and OpenStreetMap on standard keystrokes. -
watershedOutput(id): The UI view wrapper strictly defining the resultingplotOutputplane. -
watershedServer(id): Generates reactive bounds that intercept the click events, calling the generalizedewing::get_watershedAPI integrations, establishing the geometry viaewing::add_watershed_hex_overlay, and visualizing via genericautoplot.
Geographic Dictionary Expansion
We developed a UI dictionary component handling internal lookups for
standard HUC12 IDs and listing out dynamically corresponding sub-feature
geometries bounds constraints. This utilizes a static CSV lookup table
(inst/extdata/watershed/huc_features.csv) matching target
HUC bounds to known physical string inputs natively. Challenge is
finding names to populate this, noting that common names may be
ambiguous and need to be resolved to specific geographic location
(county, state).
Dynamic GIS Discovery (Option B): To facilitate
populating this static dictionary algorithmically, we implemented a
standalone backend utility
discover_watershed_features(huc_id) natively inside
R/watershed.R. By pulling the USGS HUC12 bounding box map
and piping it directly into osmdata, it dynamically
executes a raw Overpass XML QL union query across targets like
natural, waterway, and leisure.
This should bypass strict API rate limits, but it seems to generate
timeout failures.
Geometry Repair & Caching Optimizations: Two critical reliability structures were implemented to guarantee mapping backend stability:
-
Topological Fixes: Because public OpenStreetMap
vectors are notoriously ill-formatted (possessing self-intersecting
loops that naturally crash intersection logic), the spatial pipeline now
securely disables Google’s strict spherical geometry engine
(
sf::sf_use_s2(FALSE)) and patches incoming structural bounds natively viasf::st_make_valid()prior to topological rendering. -
Reactive API Caching: The Shiny UI was decoupled to
drastically reduce network payloads to the USGS grid. By formally
adapting
get_watershed()to intercept pre-fetched shapes, we extractednhdplusTools::get_huc()into a dedicated genericbase_hucreactive. Now, modifying the overlay feature name simply pulls the identical map topographical foundation from internal memory rather than executing sequential 5-second internet fetches!
6. Interactive Hexagonal Watershed App
(R/hexmapApp.R)
Combining inst/scripts/watershed_overlay.R with
R/leafletApp.R, we developed R/hexmapApp.R
using Shiny Module Composition to provide a unified
pipeline connecting interactive Leaflet feature discovery with HUC12
boundary lookup, feature area restriction clipping, and hexagonal
substrate grid overlays.
Key Features & Architecture
-
Modular Shiny Composition:
- Rather than duplicating map handling,
hexmapApp.Rcomposes theleafletInput(),leafletOutput(), andleafletServer()modules fromR/leafletApp.R. -
leafletServer()returns a reactive list (huc,status,click), allowinghexmapServer()to seamlessly receive the user’s clicked watershed boundary.
- Rather than duplicating map handling,
-
Feature Isolation & Polygon Clipping:
- For HUCs containing extensive open water or surrounding land (e.g.,
Isle Royale HUC
041800000101),get_watershed(huc_id, feature_name)usesosmdata::getbb(feature_name)to download the feature boundary (e.g. island polygon) and intersects it (st_intersection) with the HUC12 boundary. - Only the restricted geographic feature geometry is retained for hexagonal substrate generation.
- For HUCs containing extensive open water or surrounding land (e.g.,
Isle Royale HUC
-
Hexagonal Mesh Generation & Multi-View
Rendering:
- Calculates spatial hexagonal grid via
add_watershed_hex_overlay(huc_info, hex_diameter). - Renders interactive vector polygons dynamically on the Leaflet map
canvas via
leafletProxyandadd_leaflet_hex_overlay(). - Simultaneously renders static
ggplot2autoplots viaautoplot.watershed_hex_overlay().
- Calculates spatial hexagonal grid via
-
Modular Package Export:
- Exported as
hexmapApp(), with modular functionshexmapInput(),hexmapOutput(), andhexmapServer().
- Exported as
7. Multi-HUC Regional Aggregation & Rubberband Polygon Selection
Building on individual HUC12 selection, we implemented user-defined spatial region selection via interactive rubberband polygon drawing, enabling the combination of adjacent subwatersheds into an aggregated regional domain.
Architecture & Workflow
-
Interactive Rubberband Polygon Drawing & Toggle Control
(
R/leaflet.R&R/leafletApp.R):- Integrated
leaflet.extras::addDrawToolbar()intobuild_base_map(), providing intuitive polygon and rectangle draw tools on the Leaflet map widget. -
Drawing Mode State Tracking
(
is_drawing):leafletServer()monitorsinput$mapper_draw_startandinput$mapper_draw_stopevents via anis_drawingreactive flag. This suppresses single-point reverse-geocoding (input$mapper_click) while placing vertex points, preventing duplicate progress bar triggers during drawing. - Inline Control Layout & Region Hiding: “Search Watersheds in Region”, “Clear Region”, and “Hide Drawn Region” controls are aligned on a single flex horizontal line. Checking “Hide Drawn Region” toggles visibility of the drawn region polygon without losing boundary coordinates.
- Integrated
-
Reverse-Geocoding & Auto-Scaling HUC Hierarchy
(
get_hucs_from_polygon):-
get_hucs_from_polygon(polygon_sf, max_hucs = 10)projects the drawn rubberband region into WGS84 coordinates and queriesnhdplusTools::get_huc(AOI = poly, type = "huc12"). -
Dynamic HUC Scaling (HUC12
HUC10
HUC8): If the drawn region covers more than
max_hucs(10) subwatersheds, the engine automatically scales up the USGS query fromhuc12to broaderhuc10orhuc8levels. This guarantees scalable regional aggregation without overwhelming server memory or API limits.
-
-
Smooth Layer Group Updating & Bi-directional
Syncing:
-
Explicit Layer Purging & Group Redrawing:
Updates invoke
leafletProxywithremoveShape(layerId = ids)andclearGroup("huc_polygons"). This explicitly purges existing SVG shapes from Leaflet JS internal memory, allowing instant style re-rendering (solid purple vs bold crimson red#C0392Bdashed"6,6") when adding back or removing HUCs. -
Robust Bi-directional Sidebar Syncing
(
R/hexmapApp.R): Populates a dynamicselectizeInput(multiple = TRUE)in the “Watershed Controls” input panel. Choices update with human-readable HUC IDs and feature names (e.g.041800000101 (Isle Royale East)). Observer logic usessetequal(unname(as.character(...)))to eliminate race conditions between dropdown updates and map shape events: adding or removing watershed tags in the sidebar dropdown instantly updates Leaflet map shape renderings, while clicking map shapes dynamically adds or removes tags in the sidebar dropdown.
-
Explicit Layer Purging & Group Redrawing:
Updates invoke
-
Regional Aggregation & Topological Unioning
(
R/watershed.R):-
get_watershed()natively accepts single HUC IDs, character vectors of HUC IDs, or pre-fetched multi-HUCsflayers. - For multi-HUC regions,
sf::st_union()merges adjacent included component subwatershed polygons into a unified boundary representation ($layer), while preserving individual component HUC metadata ($individual_hucs).
-
-
Continuous Substrate Mesh Generation & Multi-View
Rendering:
-
add_watershed_hex_overlay()generates a continuous hexagonal substrate grid spanning the aggregated multi-HUC regional polygon. -
add_leaflet_hex_overlay()renders component HUC boundaries in dashed lines, outer combined region boundaries in solid blue, and the unified hex mesh. -
autoplot.watershed_hex_overlay()renders staticggplot2autoplots featuring dashed component HUC boundaries and continuous regional hex overlays.
-