The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Non-dendritic networks

dblodgett@usgs.gov

Introduction

vignette("hydroloom") and vignette("advanced_network") cover the basics of network topology representation and the attributes that build on a strictly dendritic network. This vignette extends those topics to the hydroloom functionality that supports non-dendritic networks.

The term “non-dendritic” refers to any network that does not follow a dendritic flow pattern. Typically that means one or more flowlines diverting from a primary flow path. Non-dendritic can also refer to endorheic basins nested within an otherwise dendritic basin — but this article addresses the diverted-flowline case.

The terms “diversion” and “divergence” are used in similar contexts but carry distinct meanings. A diversion is a diverted flowline (waterbody), whereas a divergence is where the network diverges. “Diversion” is often associated with anthropogenic features, but a diversion can also be a naturally occurring diverted flow.

Non-dendritic topology attributes

In a non-dendritic network, one downstream path at each divergence is treated as primary and the others as secondary. hydroloom supports several attributes for tracking that primary/secondary categorization.

fromnode and tonode

fromnode and tonode store a flow network as an edge-node topology where every feature has exactly one upstream node and exactly one downstream node. Nodes give every confluence and divergence a single identifier, which makes converting a flow network to a graph and many downstream analyses cleaner.

divergence

The divergence attribute indicates if a downstream connection is primary (1) or secondary (2). If 0, a connection is not downstream of a divergence. This attribute is useful as it facilitates following a flow network in the “downstream mainstem” direction at every divergence.

return divergence

The return divergence attribute indicates that one or more of the features upstream of a given feature originates from a divergence. If 0, the upstream features are not part of a diversion. If 1, one or more of the upstream features is part of a diversion.

stream calculator

The stream calculator attribute is part of the modified Strahler stream order as implemented in the NHDPlus data model. It indicates if a given feature is part of the downstream mainstem dendritic network or is part of a diverted path. If 0, the path is part of a diversion. Otherwise stream calculator will be equal to stream order. When generating Strahler stream order, if stream calculator is 0 for a given feature, that feature is not considered for incrementing downstream stream order.

summary

As a system, stream calculator, divergence and return divergence support network navigation and processing in the context of diverted paths.

  1. A feature at the top of a diversion will have divergence set to 1.
  2. All features that are part of a diversion that has not yet recombined with a main path, will have stream calculator set to 0.
  3. A feature that is just downstream of where a diversion recombines with a main path will have return divergence set to 1.

Divergence case study: dropped vs preserved secondary paths

Before applying the full pipeline to real data, look at what happens to a divergence when a non-dendritic network is forced into the dendritic hy_topo form. The five-edge example from vignette("hydroloom") has one divergence at feature 1 and one confluence at feature 5.

We start in hy_node form with fromnode/tonode and a divergence column marking the secondary path. The network looks like this, with feature 1 flowing into node N2, where it splits into feature 2 (main path) and feature 4 (diverted secondary path), both of which rejoin at node N4 and flow out through feature 5:

       N1
        |
        1
        | 
        v
       N2
      /  \
     2    4
    /      \ (4 is diverted)
   v        v
   N3       |
    \       |
     3      | 
      \    /
       v  v
        N4
         |
         5
         |
         v
         N5
library(hydroloom)

node_df <- data.frame(
  id         = c(1, 2, 3, 4, 5),
  fromnode   = c("N1", "N2", "N3", "N2", "N4"),
  tonode     = c("N2", "N3", "N4", "N4", "N5"),
  divergence = c(0,  2,  0,  1,  0)
)

x_node <- hy(node_df)
class(x_node)
#> [1] "hy_node"    "hy"         "tbl_df"     "tbl"        "data.frame"
nrow(x_node)
#> [1] 5

Converting to a dendritic edge list with add_toids(return_dendritic = TRUE) keeps one row per feature and drops the secondary downstream connection from feature 1:

x_topo <- add_toids(x_node, return_dendritic = TRUE)
class(x_topo)
#> [1] "hy_topo"    "hy"         "tbl_df"     "tbl"        "data.frame"
nrow(x_topo)
#> [1] 5
x_topo[, c("id", "toid")]
#> # hydroloom dendritic edge list (self-referencing): 5 features
#> # A tibble: 5 × 2
#>      id  toid
#>   <dbl> <dbl>
#> 1     1     4
#> 2     2     3
#> 3     3     5
#> 4     4     5
#> 5     5     0

Setting return_dendritic = FALSE preserves both downstream connections by repeating id == 1. The result is no longer dendritic, so hy() classifies it as hy_flownetwork:

x_fn <- add_toids(x_node, return_dendritic = FALSE)
class(x_fn)
#> [1] "hy_flownetwork" "tbl_df"         "tbl"            "data.frame"
nrow(x_fn)
#> [1] 6
x_fn[, c("id", "toid")]
#> # hydroloom flow network: 6 connections, 1 non-dendritic edges
#> # A tibble: 6 × 2
#>      id  toid
#>   <dbl> <dbl>
#> 1     1     2
#> 2     1     4
#> 3     2     3
#> 4     3     5
#> 5     4     5
#> 6     5     0

Both downstream paths from feature 1 are preserved. hy_capabilities() shows which functions are callable at each stage; that workflow is demonstrated end to end in vignette("network_navigation").

Bringing it all together

The example below shows how we can recreate the non-dendritic attributes and use them in practice.

We’ll start with the small sample watershed that’s included in hydroloom and select only the attributes required to recreate the non-dendritic network.

x <- sf::read_sf(system.file("extdata/new_hope.gpkg",
  package = "hydroloom"))

# First we select only an id, a name, and a feature type.
flow_net <- x |>
  select(COMID, GNIS_ID, FTYPE) |>
  sf::st_transform(5070)

# Now we convert the geometric network to an attribute topology
# and convert that to a node topology and join our attributes back
flow_net <- flow_net |>
  make_attribute_topology(min_distance = 5) |>
  hydroloom::make_node_topology(add_div = TRUE) |>
  left_join(sf::st_drop_geometry(flow_net), by = "COMID")

# We only have one outlet so it doesn't matter if it is coastal
# or inland but we have to provide it.
outlets <- filter(flow_net, !tonode %in% fromnode)

# We have these feature types. A larger dataset might include
# things like canals which would not be considered  "major"
unique(flow_net$FTYPE)
#> [1] "StreamRiver"    "Connector"      "ArtificialPath"

# compare dendritic vs non-dendritic toid row counts to see how many
# secondary paths exist in the data
flow_net_with_div <- add_divergence(flow_net,
  coastal_outlet_ids = c(),
  inland_outlet_ids = outlets$COMID,
  name_attr = "GNIS_ID",
  type_attr = "FTYPE",
  major_types = unique(flow_net$FTYPE))

dend <- add_toids(flow_net_with_div, return_dendritic = TRUE)
nondend <- add_toids(flow_net_with_div, return_dendritic = FALSE)

nrow(dend)
#> [1] 746
nrow(nondend)
#> [1] 832
nrow(nondend) - nrow(dend)
#> [1] 86

# now we run the add_divergence, add_toids, and add_streamorder
flow_net <- add_divergence(flow_net,
  coastal_outlet_ids = c(),
  inland_outlet_ids = outlets$COMID,
  name_attr = "GNIS_ID",
  type_attr = "FTYPE",
  major_types = unique(flow_net$FTYPE)) |>
  add_toids() |>
  add_streamorder() |>
  add_return_divergence()

# Make sure we reproduce what came from our source NHDPlus data.
sum(flow_net$divergence == 2)
#> [1] 84
sum(x$Divergence == 2)
#> [1] 84
all(flow_net$divergence == x$Divergence)
#> [1] TRUE
sum(flow_net$return_divergence == x$RtnDiv)
#> [1] 745

names(flow_net)
#>  [1] "COMID"             "toid"              "tonode"           
#>  [4] "GNIS_ID"           "FTYPE"             "divergence"       
#>  [7] "fromnode"          "stream_order"      "stream_calculator"
#> [10] "return_divergence"

With the above code, we removed all attributes other than an ID, a name and a feature type and recreated both a dendritic (toid) and non-dendritic (fromnode tonode) topology. We added divergence attribute, stream_order, stream_calculator, and return_divergence attributes.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.