Skip to content

Commit d5a9f57

Browse files
author
Mike Johnson
authored
Merge pull request #1 from lynker-spatial/feat-toposort
Add `add_hydroseq` network function
2 parents a5c4d9f + c4341bf commit d5a9f57

3 files changed

Lines changed: 93 additions & 0 deletions

File tree

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ S3method(dbplyr_edition,OGRSQLConnection)
66
S3method(st_as_sf,tbl_OGRSQLConnection)
77
export(OGRSQL)
88
export(accumulate_downstream)
9+
export(add_hydroseq)
910
export(as_ogr)
1011
export(collect)
1112
export(st_as_sf)
@@ -58,6 +59,8 @@ importFrom(httr,GET)
5859
importFrom(httr,progress)
5960
importFrom(httr,write_disk)
6061
importFrom(igraph,as_ids)
62+
importFrom(igraph,dfs)
63+
importFrom(igraph,graph_from_data_frame)
6164
importFrom(igraph,graph_from_edgelist)
6265
importFrom(igraph,is_dag)
6366
importFrom(igraph,topo_sort)

R/network_properties.R

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,60 @@ accumulate_downstream <- function(x, id = "flowpath_id", toid = "flowpath_toid
9292
# Return totals aligned to input rows
9393
as.numeric(total[idx])
9494
}
95+
96+
97+
#' Compute and add the hydrosequence to a directed acyclic network.
98+
#'
99+
#' @param topology A data frame (or tibble) containing at least the identifier column
100+
#' given by `id` and the downstream pointer column given by `toid`.
101+
#' @param id Character scalar. Column name in `topology` with unique node identifiers.
102+
#' Defaults to `"flowpath_id"`.
103+
#' @param toid Character scalar. Column name in `topology` with the *downstream* node
104+
#' identifier for each row. Use `NA` or `0` for outlets/terminals.
105+
#' Defaults to `"flowpath_toid"`.
106+
#' @param colname Character scalar. Column name to use in result.
107+
#' Defaults to `"hydroseq"`
108+
#'
109+
#' @returns The data frame `topology` with an additional column, named `colname`, representing the hydrosequence.
110+
#' @importFrom igraph dfs graph_from_data_frame
111+
#' @export
112+
add_hydroseq <- function(topology, id = "flowpath_id", toid = "flowpath_toid", colname = "hydroseq") {
113+
# Create a _transposed_ network, where traversing the network
114+
# is equivalent to traversing the hydrological network upstream.
115+
#
116+
# This assumes the outlets of this network all connect to an
117+
# ephemeral "0" node (forming a rooted tree network).
118+
edgelist <- topology[, c(toid, id)]
119+
names(edgelist) <- c("id", "toid")
120+
edgelist$id[is.na(edgelist$id)] <- "0"
121+
122+
# TODO: Check if multiple components exist. If they do, then
123+
# we need to add "0" edges for each component not rooted on "0".
124+
125+
# Perform DFS from each terminal upstream to get a
126+
# distinct topological sort for the hydrosequence.
127+
sorted <- data.frame(
128+
node = as.integer(
129+
names(
130+
igraph::dfs(
131+
igraph::graph_from_data_frame(edgelist),
132+
root = "0",
133+
mode = "out"
134+
)$order
135+
)
136+
)
137+
)
138+
139+
sorted$hydroseq <- c(0, seq_len(nrow(sorted) - 1))
140+
141+
# Merge the initial hydrosequence to the edgelist and handle ties in the hydrosequence.
142+
result <- merge(edgelist, sorted, by.x = "id", by.y = "node", all.x = TRUE)
143+
result <- result[!is.na(result$hydroseq), ]
144+
result <- result[order(result$hydroseq, result$id, result$toid), c("toid", "id")]
145+
result$hydroseq <- seq_len(nrow(result))
146+
names(result) <- c(id, toid, "hydroseq")
147+
148+
# Arrange into input order
149+
topology[[colname]] <- result$hydroseq[match(topology[[id]], result[[id]])]
150+
topology
151+
}

man/add_hydroseq.Rd

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)