|
| 1 | +#' @title Aggregate flows throughout a network. |
| 2 | +#' |
| 3 | +#' @description Aggregate flows throughout a network based on an input matrix |
| 4 | +#' of flows between all pairs of `from` and `to` points. Flows are calculated |
| 5 | +#' by default on contracted graphs, via the `contract = TRUE` parameter. (These |
| 6 | +#' are derived by reducing the input graph down to junction vertices only, by |
| 7 | +#' joining all intermediate edges between each junction.) If changes to the |
| 8 | +#' input graph do not prompt changes to resultant flows, and the default |
| 9 | +#' `contract = TRUE` is used, it may be that calculations are using previously |
| 10 | +#' cached versions of the contracted graph. If so, please use either |
| 11 | +#' \link{clear_dodgr_cache} to remove the cached version, or |
| 12 | +#' \link{dodgr_cache_off} prior to initial graph construction to switch the |
| 13 | +#' cache off completely. |
| 14 | +#' |
| 15 | +#' @param graph `data.frame` or equivalent object representing the network |
| 16 | +#' graph (see Details) |
| 17 | +#' @param flows Matrix of flows with `nrow(flows)==length(from)` and |
| 18 | +#' `ncol(flows)==length(to)`. |
| 19 | +#' @param pairwise If `TRUE`, aggregate flows only only paths connecting the |
| 20 | +#' ordered pairs of `from` and `to`. In this case, both `from` and `to` must be |
| 21 | +#' of the same length, and `flows` must be either a vector of the same length, |
| 22 | +#' or a matrix with only one column and same number of rows. `flows` then |
| 23 | +#' quantifies the flows between each pair of `from` and `to` points. |
| 24 | +#' @param contract If `TRUE` (default), calculate flows on contracted graph |
| 25 | +#' before mapping them back on to the original full graph (recommended as this |
| 26 | +#' will generally be much faster). `FALSE` should only be used if the `graph` |
| 27 | +#' has already been contracted. |
| 28 | +#' @param heap Type of heap to use in priority queue. Options include |
| 29 | +#' Fibonacci Heap (default; `FHeap`), Binary Heap (`BHeap`), |
| 30 | +#' Trinomial Heap (`TriHeap`), Extended Trinomial Heap |
| 31 | +#' (`TriHeapExt`, and 2-3 Heap (`Heap23`). |
| 32 | +#' @param tol Relative tolerance below which flows towards `to` vertices are not |
| 33 | +#' considered. This will generally have no effect, but can provide speed gains |
| 34 | +#' when flow matrices represent spatial interaction models, in which case this |
| 35 | +#' parameter effectively reduces the radius from each `from` point over which |
| 36 | +#' flows are aggregated. To remove any such effect, set `tol = 0`. |
| 37 | +#' @param norm_sums Standardise sums from all origin points, so sum of flows |
| 38 | +#' throughout entire network equals sum of densities from all origins (see |
| 39 | +#' Note). |
| 40 | +#' @inheritParams dodgr_dists |
| 41 | +#' @return Modified version of graph with additional `flow` column added. |
| 42 | +#' |
| 43 | +#' @note The `norm_sums` parameter should be used whenever densities at origins |
| 44 | +#' and destinations are absolute values, and ensures that the sum of resultant |
| 45 | +#' flow values throughout the entire network equals the sum of densities at all |
| 46 | +#' origins. For example, with `norm_sums = TRUE` (the default), a flow from a |
| 47 | +#' single origin with density one to a single destination along two edges will |
| 48 | +#' allocate flows of one half to each of those edges, such that the sum of flows |
| 49 | +#' across the network will equal one, or the sum of densities from all origins. |
| 50 | +#' The `norm_sums = TRUE` option is appropriate where densities are relative |
| 51 | +#' values, and ensures that each edge maintains relative proportions. In the |
| 52 | +#' above example, flows along each of two edges would equal one, for a network |
| 53 | +#' sum of two, or greater than the sum of densities. |
| 54 | +#' |
| 55 | +#' Flows are calculated by default using parallel computation with the maximal |
| 56 | +#' number of available cores or threads. This number can be reduced by |
| 57 | +#' specifying a value via |
| 58 | +#' `RcppParallel::setThreadOptions (numThreads = <desired_number>)`. |
| 59 | +#' |
| 60 | +#' @family flows |
| 61 | +#' @export |
| 62 | +#' @examples |
| 63 | +#' graph <- weight_streetnet (hampi) |
| 64 | +#' from <- sample (graph$from_id, size = 10) |
| 65 | +#' to <- sample (graph$to_id, size = 5) |
| 66 | +#' to <- to [!to %in% from] |
| 67 | +#' flows <- matrix (10 * runif (length (from) * length (to)), |
| 68 | +#' nrow = length (from) |
| 69 | +#' ) |
| 70 | +#' graph <- dodgr_flows_aggregate (graph, from = from, to = to, flows = flows) |
| 71 | +#' # graph then has an additonal 'flows' column of aggregate flows along all |
| 72 | +#' # edges. These flows are directed, and can be aggregated to equivalent |
| 73 | +#' # undirected flows on an equivalent undirected graph with: |
| 74 | +#' graph_undir <- merge_directed_graph (graph) |
| 75 | +#' # This graph will only include those edges having non-zero flows, and so: |
| 76 | +#' nrow (graph) |
| 77 | +#' nrow (graph_undir) # the latter is much smaller |
| 78 | +#' |
| 79 | +#' # The following code can be used to convert the resultant graph to an `sf` |
| 80 | +#' # object suitable for plotting |
| 81 | +#' \dontrun{ |
| 82 | +#' gsf <- dodgr_to_sf (graph_undir) |
| 83 | +#' |
| 84 | +#' # example of plotting with the 'mapview' package |
| 85 | +#' library (mapview) |
| 86 | +#' flow <- gsf$flow / max (gsf$flow) |
| 87 | +#' ncols <- 30 |
| 88 | +#' cols <- c ("lawngreen", "red") |
| 89 | +#' colranmp <- colorRampPalette (cols) (ncols) [ceiling (ncols * flow)] |
| 90 | +#' mapview (gsf, color = colranmp, lwd = 10 * flow) |
| 91 | +#' } |
| 92 | +#' |
| 93 | +#' # An example of flow aggregation across a generic (non-OSM) highway, |
| 94 | +#' # represented as the `routes_fast` object of the \pkg{stplanr} package, |
| 95 | +#' # which is a SpatialLinesDataFrame containing commuter densities along |
| 96 | +#' # components of a street network. |
| 97 | +#' \dontrun{ |
| 98 | +#' library (stplanr) |
| 99 | +#' # merge all of the 'routes_fast' lines into a single network |
| 100 | +#' r <- overline (routes_fast, attrib = "length", buff_dist = 1) |
| 101 | +#' r <- sf::st_as_sf (r) |
| 102 | +#' # then extract the start and end points of each of the original 'routes_fast' |
| 103 | +#' # lines and use these for routing with `dodgr` |
| 104 | +#' l <- lapply (routes_fast@lines, function (i) { |
| 105 | +#' c ( |
| 106 | +#' sp::coordinates (i) [[1]] [1, ], |
| 107 | +#' tail (sp::coordinates (i) [[1]], 1) |
| 108 | +#' ) |
| 109 | +#' }) |
| 110 | +#' l <- do.call (rbind, l) |
| 111 | +#' xy_start <- l [, 1:2] |
| 112 | +#' xy_end <- l [, 3:4] |
| 113 | +#' # Then just specify a generic OD matrix with uniform values of 1: |
| 114 | +#' flows <- matrix (1, nrow = nrow (l), ncol = nrow (l)) |
| 115 | +#' # We need to specify both a `type` and `id` column for the |
| 116 | +#' # \link{weight_streetnet} function. |
| 117 | +#' r$type <- 1 |
| 118 | +#' r$id <- seq (nrow (r)) |
| 119 | +#' graph <- weight_streetnet ( |
| 120 | +#' r, |
| 121 | +#' type_col = "type", |
| 122 | +#' id_col = "id", |
| 123 | +#' wt_profile = 1 |
| 124 | +#' ) |
| 125 | +#' f <- dodgr_flows_aggregate ( |
| 126 | +#' graph, |
| 127 | +#' from = xy_start, |
| 128 | +#' to = xy_end, |
| 129 | +#' flows = flows |
| 130 | +#' ) |
| 131 | +#' # Then merge directed flows and convert to \pkg{sf} for plotting as before: |
| 132 | +#' f <- merge_directed_graph (f) |
| 133 | +#' geoms <- dodgr_to_sfc (f) |
| 134 | +#' gc <- dodgr_contract_graph (f) |
| 135 | +#' gsf <- sf::st_sf (geoms) |
| 136 | +#' gsf$flow <- gc$flow |
| 137 | +#' # sf plot: |
| 138 | +#' plot (gsf ["flow"]) |
| 139 | +#' } |
| 140 | +dodgr_flows_aggregate <- function (graph, |
| 141 | + from, |
| 142 | + to, |
| 143 | + flows, |
| 144 | + pairwise = FALSE, |
| 145 | + contract = TRUE, |
| 146 | + heap = "BHeap", |
| 147 | + tol = 1e-12, |
| 148 | + norm_sums = TRUE, |
| 149 | + quiet = TRUE) { |
| 150 | + |
| 151 | + if (methods::is (graph, "dodgr_contracted")) { |
| 152 | + contract <- FALSE |
| 153 | + } |
| 154 | + |
| 155 | + if (anyNA (flows)) { |
| 156 | + flows [is.na (flows)] <- 0 |
| 157 | + } |
| 158 | + hps <- get_heap (heap, graph) |
| 159 | + heap <- hps$heap |
| 160 | + graph <- hps$graph |
| 161 | + |
| 162 | + if (!identical (class (from), class (to))) { |
| 163 | + stop ("from and to must be the same class of object.") |
| 164 | + } |
| 165 | + check_for_flow_col (graph) |
| 166 | + |
| 167 | + graph <- preprocess_spatial_cols (graph) |
| 168 | + gr_cols <- dodgr_graph_cols (graph) |
| 169 | + |
| 170 | + to_from_indices <- to_from_index_with_tp (graph, from, to) |
| 171 | + if (to_from_indices$compound) { |
| 172 | + graph <- to_from_indices$graph_compound |
| 173 | + } |
| 174 | + |
| 175 | + if (contract) { |
| 176 | + graph_full <- graph |
| 177 | + graph <- contract_graph_with_pts ( |
| 178 | + graph, |
| 179 | + to_from_indices$from$id, |
| 180 | + to_from_indices$to$id |
| 181 | + ) |
| 182 | + hashc <- get_hash (graph, contracted = TRUE) |
| 183 | + fname_c <- fs::path ( |
| 184 | + fs::path_temp (), |
| 185 | + paste0 ("dodgr_edge_map_", hashc, ".Rds") |
| 186 | + ) |
| 187 | + if (!fs::file_exists (fname_c)) { |
| 188 | + stop ("something went wrong extracting the edge_map ... ") |
| 189 | + } # nocov |
| 190 | + edge_map <- readRDS (fname_c) |
| 191 | + } |
| 192 | + |
| 193 | + graph2 <- convert_graph (graph, gr_cols) |
| 194 | + |
| 195 | + if (!is.matrix (flows)) { |
| 196 | + flows <- matrix (flows, nrow = length (to_from_indices$from$index)) |
| 197 | + } else if (!(nrow (flows) == length (to_from_indices$from$index) && |
| 198 | + ncol (flows) == length (to_from_indices$to$index))) { |
| 199 | + stop ("flows matrix is not compatible with 'from'/'to' arguments") |
| 200 | + } |
| 201 | + if (pairwise) { |
| 202 | + check_pairwise_from_to (from, to, flows) |
| 203 | + } |
| 204 | + |
| 205 | + if (!quiet) { |
| 206 | + message ("\nAggregating flows ... ", appendLF = FALSE) |
| 207 | + } |
| 208 | + |
| 209 | + if (pairwise) { |
| 210 | + graph$flow <- rcpp_flows_aggregate_pairwise ( |
| 211 | + graph2, to_from_indices$vert_map, |
| 212 | + to_from_indices$from$index, to_from_indices$to$index, |
| 213 | + flows, norm_sums, tol, heap |
| 214 | + ) |
| 215 | + } else { |
| 216 | + graph$flow <- rcpp_flows_aggregate_par ( |
| 217 | + graph2, to_from_indices$vert_map, |
| 218 | + to_from_indices$from$index, to_from_indices$to$index, |
| 219 | + flows, norm_sums, tol, heap |
| 220 | + ) |
| 221 | + } |
| 222 | + |
| 223 | + if (contract) { # map contracted flows back onto full graph |
| 224 | + graph <- uncontract_graph (graph, edge_map, graph_full) |
| 225 | + graph$flow [is.na (graph$flow)] <- 0 |
| 226 | + } |
| 227 | + if (to_from_indices$compound) { |
| 228 | + graph <- uncompound_junctions ( |
| 229 | + graph, |
| 230 | + "flow", |
| 231 | + to_from_indices$compound_junction_map |
| 232 | + ) |
| 233 | + graph$flow [is.na (graph$flow)] <- 0 |
| 234 | + } |
| 235 | + |
| 236 | + return (graph) |
| 237 | +} |
0 commit comments