Distances on Directed Graphs in R
1#include "graph.h"
2
3//' get_to_from
4//'
5//' Get one pair of two and from edges and vertices. Main task is to make sure
6//' that bi-directed edges ("intermediate_double") correctly return the
7//' **different** values of from and to vertices and edges.
8//'
9//' @noRd
10void graph_contract::get_to_from (const edge_map_t &edge_map,
11 const std::unordered_set <edge_id_t> &edges,
12 const std::vector <vertex_id_t> &two_nbs,
13 vertex_id_t &vt_from, vertex_id_t &vt_to,
14 edge_id_t &edge_from_id, edge_id_t &edge_to_id)
15{
16 for (auto edge_id: edges)
17 {
18 edge_t edge = edge_map.find (edge_id)->second;
19 if (edge_from_id == "")
20 {
21 if (edge.get_from_vertex () == two_nbs [0] &&
22 (edge_to_id == "" || vt_to != two_nbs [0]))
23 {
24 edge_from_id = edge_id;
25 vt_from = two_nbs [0];
26 } else if (edge.get_from_vertex () == two_nbs [1] &&
27 (edge_to_id == "" || vt_to != two_nbs [1]))
28 {
29 edge_from_id = edge_id;
30 vt_from = two_nbs [1];
31 }
32 }
33 if (edge_to_id == "")
34 {
35 if (edge.get_to_vertex () == two_nbs [0] &&
36 (edge_from_id == "" || vt_from != two_nbs [0]))
37 {
38 edge_to_id = edge_id;
39 vt_to = two_nbs [0];
40 } else if (edge.get_to_vertex () == two_nbs [1] &&
41 (edge_from_id == "" || vt_from != two_nbs [1]))
42 {
43 edge_to_id = edge_id;
44 vt_to = two_nbs [1];
45 }
46 }
47 }
48}
49
50void graph_contract::contract_one_edge (vert2edge_map_t &vert2edge_map,
51 vertex_map_t &vertex_map, edge_map_t &edge_map,
52 const std::unordered_set <edge_id_t> &edgelist,
53 const vertex_id_t vtx_id, const vertex_id_t vt_from,
54 const vertex_id_t vt_to,
55 const edge_id_t edge_from_id, const edge_id_t edge_to_id,
56 const edge_id_t new_edge_id,
57 bool has_times)
58{
59 edge_t edge_from = edge_map.find (edge_from_id)->second,
60 edge_to = edge_map.find (edge_to_id)->second;
61 double time = -INFINITE_DOUBLE, timew = -INFINITE_DOUBLE,
62 d = edge_from.dist + edge_to.dist,
63 w = edge_from.weight + edge_to.weight;
64 if (has_times)
65 {
66 time = edge_from.time + edge_to.time;
67 timew = edge_from.timew + edge_to.timew;
68 }
69
70 std::vector <edge_id_t> old_edges,
71 old_edges_fr = edge_from.get_old_edges (),
72 old_edges_to = edge_to.get_old_edges ();
73 for (auto e: old_edges_fr)
74 old_edges.push_back (e);
75 for (auto e: old_edges_to)
76 old_edges.push_back (e);
77 // Only insert edge IDs that are in the original list
78 if (edgelist.find (edge_from_id) != edgelist.end ())
79 old_edges.push_back (edge_from_id);
80 if (edgelist.find (edge_to_id) != edgelist.end ())
81 old_edges.push_back (edge_to_id);
82
83 graph::erase_from_v2e_map (vert2edge_map, vtx_id, edge_from_id);
84 graph::erase_from_v2e_map (vert2edge_map, vtx_id, edge_to_id);
85 graph::erase_from_v2e_map (vert2edge_map, vt_from, edge_from_id);
86 graph::erase_from_v2e_map (vert2edge_map, vt_to, edge_to_id);
87 graph::add_to_v2e_map (vert2edge_map, vt_from, new_edge_id);
88 graph::add_to_v2e_map (vert2edge_map, vt_to, new_edge_id);
89
90 vertex_t vt = vertex_map [vt_from];
91 vt.replace_neighbour (vtx_id, vt_from);
92 vertex_map [vt_from] = vt;
93 vt = vertex_map [vt_to];
94 vt.replace_neighbour (vtx_id, vt_to);
95 vertex_map [vt_to] = vt;
96
97 edge_map.erase (edge_from_id);
98 edge_map.erase (edge_to_id);
99 std::vector <double> wts;
100 if (!has_times)
101 wts = {d, w};
102 else
103 wts = {d, w, time, timew};
104 edge_t new_edge = edge_t (vt_from, vt_to, wts, new_edge_id, old_edges);
105 edge_map.emplace (new_edge_id, new_edge);
106}
107
108//' same_hwy_type
109//'
110//' Determine whether two edges represent the same weight category (type of
111//' highway for street networks, for example). Categories are not retained in
112//' converted graphs, but can be discerned by comparing ratios of weighted to
113//' non-weighted distances.
114//' @noRd
115bool graph_contract::same_hwy_type (const edge_map_t &edge_map,
116 const edge_id_t &e1, const edge_id_t &e2)
117{
118 const double tol = 1.0e-6;
119
120 edge_t edge1 = edge_map.find (e1)->second,
121 edge2 = edge_map.find (e2)->second;
122
123 return (fabs (edge1.weight / edge1.dist - edge2.weight / edge2.dist) < tol);
124}
125
126
127// See docs/graph-contraction for explanation of the following code and
128// associated vertex and edge maps.
129void graph_contract::contract_graph (vertex_map_t &vertex_map,
130 edge_map_t &edge_map, vert2edge_map_t &vert2edge_map,
131 std::unordered_set <vertex_id_t> verts_to_keep,
132 bool has_times)
133{
134 std::unordered_set <vertex_id_t> verts;
135 for (auto v: vertex_map)
136 {
137 if (verts_to_keep.find (v.first) == verts_to_keep.end ())
138 verts.insert (v.first);
139 }
140 std::unordered_set <edge_id_t> edgelist;
141 for (auto e: edge_map)
142 edgelist.insert (e.first);
143
144 std::vector <edge_id_t> new_edge_ids;
145
146 unsigned int maxid = 123456;
147
148 while (verts.size () > 0)
149 {
150 Rcpp::checkUserInterrupt ();
151 std::unordered_set <vertex_id_t>::iterator vid = verts.begin ();
152 vertex_id_t vtx_id = vertex_map.find (*vid)->first;
153 vertex_t vtx = vertex_map.find (*vid)->second;
154 std::unordered_set <edge_id_t> edges = vert2edge_map [vtx_id];
155
156 new_edge_ids.clear ();
157 new_edge_ids.push_back ("a" + std::to_string (maxid++));
158
159 if ((vtx.is_intermediate_single () || vtx.is_intermediate_double ()) &&
160 (edges.size () == 2 || edges.size () == 4))
161 {
162 if (edges.size () == 4) // is_intermediate_double as well!
163 new_edge_ids.push_back ("a" + std::to_string (maxid++));
164
165 // Get the two adjacent vertices
166 std::unordered_set <vertex_id_t> nbs = vtx.get_all_neighbours ();
167 std::vector <vertex_id_t> two_nbs;
168 two_nbs.reserve (2);
169 for (vertex_id_t nb: nbs)
170 two_nbs.push_back (nb); // size is always 2
171
172 // ensure two edges are same highway type
173 bool hwys_are_same = true;
174 for (edge_id_t new_edge_id: new_edge_ids)
175 {
176 vertex_id_t vt_from = "", vt_to = "";
177 edge_id_t edge_from_id = "", edge_to_id = "";
178 graph_contract::get_to_from (edge_map, edges, two_nbs,
179 vt_from, vt_to, edge_from_id, edge_to_id);
180 hwys_are_same = graph_contract::same_hwy_type (edge_map,
181 edge_from_id, edge_to_id);
182 if (!hwys_are_same)
183 break;
184 }
185
186 if (hwys_are_same)
187 {
188 // remove intervening vertex:
189 vertex_t vt0 = vertex_map [two_nbs [0]];
190 vertex_t vt1 = vertex_map [two_nbs [1]];
191 // Note that replace neighbour includes bi-directional
192 // replacement, so this works for intermediate_double() too
193 vt0.replace_neighbour (vtx_id, two_nbs [1]);
194 vt1.replace_neighbour (vtx_id, two_nbs [0]);
195 vertex_map [two_nbs [0]] = vt0;
196 vertex_map [two_nbs [1]] = vt1;
197
198 // construct new edge(s) and remove old ones. There are 2
199 // new_edge_ids only for intermediate double vertices
200 // (that is, bi-directional).
201 for (edge_id_t new_edge_id: new_edge_ids)
202 {
203 vertex_id_t vt_from = "", vt_to = "";
204 edge_id_t edge_from_id = "", edge_to_id = "";
205
206 // get the from and to edges and vertices
207 graph_contract::get_to_from (edge_map, edges, two_nbs,
208 vt_from, vt_to, edge_from_id, edge_to_id);
209
210 edges.erase (edge_from_id);
211 edges.erase (edge_to_id);
212 // It is possible to have repeated values of two_nbs;
213 // calling these a second time should then just lead to no
214 // contraction
215 if ((vt_from == "") || (vt_to == ""))
216 continue;
217
218 graph_contract::contract_one_edge (vert2edge_map,
219 vertex_map, edge_map, edgelist, vtx_id, vt_from,
220 vt_to, edge_from_id, edge_to_id, new_edge_id,
221 has_times);
222 }
223 }
224 }
225 verts.erase (vtx_id);
226 }
227}
228
229// Sort a vector of edges according to original edge sequence
230void graph_contract::sort_edges (
231 const std::vector <edge_id_t> &old_edges,
232 const std::unordered_map <edge_id_t, size_t> &edge_sequence,
233 std::vector <edge_id_t> &edges_sorted) {
234
235 std::vector <size_t> old_edge_index (old_edges.size ());
236 std::set <size_t> index_tmp;
237 for (size_t i = 0; i < old_edges.size (); i++)
238 {
239 old_edge_index [i] = edge_sequence.find (old_edges [i])->second;
240 index_tmp.emplace (old_edge_index [i]);
241 }
242
243 // Convert ordered index_tmp into sorted vector index:
244 std::vector <size_t> index (old_edges.size ());
245 for (size_t i = 0; i < old_edges.size (); i++) {
246 index [i] = static_cast <size_t> (std::distance (index_tmp.begin (),
247 index_tmp.find (old_edge_index [i])));
248 }
249
250 // Finally use that index to re-order old_edges:
251 for (size_t i = 0; i < old_edges.size (); i++) {
252 edges_sorted [index [i] ] = old_edges [i];
253 }
254}
255
256
257//' rcpp_contract_graph
258//'
259//' Removes nodes and edges from a graph that are not needed for routing
260//'
261//' @param graph graph to be processed
262//'
263//' @return \code{Rcpp::List} containing one \code{data.frame} with the
264//' contracted graph, one \code{data.frame} with the original graph and one
265//' \code{data.frame} containing information about the relating edge ids of the
266//' original and contracted graph.
267//'
268//' @noRd
269// [[Rcpp::export]]
270Rcpp::List rcpp_contract_graph (const Rcpp::DataFrame &graph,
271 Rcpp::Nullable <Rcpp::StringVector> &vertlist_in)
272{
273 std::unordered_set <vertex_id_t> verts_to_keep;
274 if (vertlist_in.isNotNull ())
275 {
276 Rcpp::StringVector vertlist (vertlist_in);
277 for (R_xlen_t i = 0; i < vertlist.length (); i ++)
278 verts_to_keep.emplace (std::string (vertlist [i]));
279 }
280
281 // Get set of all original edge IDs, as well as a map of original edge
282 // sequence numbers to ensure contracted edges follow original sequences
283 // (used below to fill the final edge_map).
284 std::unordered_set <edge_id_t> original_edges;
285 Rcpp::StringVector edge_id = graph ["edge_id"];
286 std::unordered_map <edge_id_t, size_t> edge_sequence;
287 size_t i = 0;
288 for (auto e: edge_id)
289 {
290 original_edges.emplace (e);
291 edge_sequence.emplace (e, i++);
292 }
293
294 vertex_map_t vertices;
295 edge_map_t edge_map;
296 vert2edge_map_t vert2edge_map;
297
298 bool has_times = graph::graph_from_df (graph, vertices, edge_map, vert2edge_map);
299
300 vertex_map_t vertices_contracted = vertices;
301 edge_map_t edge_map_contracted = edge_map;
302
303 graph_contract::contract_graph (vertices_contracted, edge_map_contracted,
304 vert2edge_map, verts_to_keep, has_times);
305
306 size_t nedges = edge_map_contracted.size ();
307
308 // These vectors are all for the contracted graph:
309 Rcpp::StringVector from_vec (nedges), to_vec (nedges),
310 edgeid_vec (nedges);
311 Rcpp::NumericVector dist_vec (nedges), weight_vec (nedges),
312 time_vec (nedges), timew_vec (nedges);
313
314 size_t map_size = 0; // size of edge map contracted -> original
315 size_t edge_count = 0;
316 for (auto e = edge_map_contracted.begin ();
317 e != edge_map_contracted.end (); ++e)
318 {
319 vertex_id_t from = e->second.get_from_vertex ();
320 vertex_id_t to = e->second.get_to_vertex ();
321 vertex_t from_vtx = vertices_contracted.at (from);
322 vertex_t to_vtx = vertices_contracted.at (to);
323
324 from_vec (edge_count) = from;
325 to_vec (edge_count) = to;
326 dist_vec (edge_count) = e->second.dist;
327 weight_vec (edge_count) = e->second.weight;
328 if (has_times)
329 {
330 time_vec (edge_count) = e->second.time;
331 timew_vec (edge_count) = e->second.timew;
332 }
333
334 edgeid_vec (edge_count) = e->second.getID ();
335
336 edge_count++;
337
338 if (original_edges.find (e->first) == original_edges.end ())
339 map_size += e->second.get_old_edges ().size ();
340 }
341
342 // populate the new -> old edge map
343 // The graph_contract::contract_one_edge function unavoidably inserts edges
344 // in random order. They have to be re-ordered here to the order in the
345 // original graph so that uncontracted edges follow original sequences
346 // (#173).
347
348 std::vector <edge_id_t> edge_map_new (map_size), edge_map_old (map_size);
349 size_t count = 0;
350 for (auto e = edge_map_contracted.begin ();
351 e != edge_map_contracted.end (); ++e)
352 {
353 if (original_edges.find (e->first) == original_edges.end ())
354 {
355 std::vector <edge_id_t> old_edges = e->second.get_old_edges ();
356 std::vector <edge_id_t> edges_sorted (old_edges.size ());
357
358 if (old_edges.size () == 1L)
359 {
360 edges_sorted [0] = old_edges [0];
361 } else
362 {
363 graph_contract::sort_edges (old_edges, edge_sequence, edges_sorted);
364 }
365
366 for (auto oe: edges_sorted)
367 {
368 edge_map_new [count] = e->first;
369 edge_map_old [count++] = oe;
370 }
371 }
372 }
373
374 Rcpp::DataFrame contracted;
375 if (!has_times)
376 contracted = Rcpp::DataFrame::create (
377 Rcpp::Named ("edge_id") = edgeid_vec,
378 Rcpp::Named ("from") = from_vec,
379 Rcpp::Named ("to") = to_vec,
380 Rcpp::Named ("d") = dist_vec,
381 Rcpp::Named ("d_weighted") = weight_vec,
382 Rcpp::_["stringsAsFactors"] = false);
383 else
384 contracted = Rcpp::DataFrame::create (
385 Rcpp::Named ("edge_id") = edgeid_vec,
386 Rcpp::Named ("from") = from_vec,
387 Rcpp::Named ("to") = to_vec,
388 Rcpp::Named ("d") = dist_vec,
389 Rcpp::Named ("d_weighted") = weight_vec,
390 Rcpp::Named ("time") = time_vec,
391 Rcpp::Named ("timew") = timew_vec,
392 Rcpp::_["stringsAsFactors"] = false);
393
394 Rcpp::DataFrame edges_new2old = Rcpp::DataFrame::create (
395 Rcpp::Named ("edge_new") = edge_map_new,
396 Rcpp::Named ("edge_old") = edge_map_old,
397 Rcpp::_["stringsAsFactors"] = false);
398
399 return Rcpp::List::create (
400 Rcpp::Named ("graph") = contracted,
401 Rcpp::Named ("edge_map") = edges_new2old);
402}
403
404//' rcpp_merge_cols
405//'
406//' Merge columns in directed graph to form aggregate undirected columns, and
407//' return a corresponding undirected graph useful for visualisation.
408//'
409//' @param graph The result of a call to \code{dodgr_flows_aggregate/disperse}
410//' or similar function resuling in columns of directed values.
411//' @return A single vector of aggregate values with non-zero values only for
412//' those edges to be retained in the directed graph.
413//'
414//' @noRd
415// [[Rcpp::export]]
416Rcpp::NumericVector rcpp_merge_cols (Rcpp::DataFrame graph)
417{
418 std::vector <std::string> from = graph ["from"];
419 std::vector <std::string> to = graph ["to"];
420 std::vector <double> dist = graph ["d"];
421 std::vector <double> wt = graph ["d_weighted"];
422 std::vector <double> aggr_var = graph ["merge"]; // always called "merge"
423
424 // vertvert_map just holds index of where pair of vertices were first found.
425 // These can only be duplicated once, so only one single value is ever
426 // needed.
427 std::unordered_map <std::string, int> vertvert_map;
428 Rcpp::NumericVector aggr_total (from.size ());
429 for (size_t i = 0; i < from.size (); i++)
430 {
431 R_xlen_t i_r = static_cast <R_xlen_t> (i);
432 std::string ft = "a" + from [i] + "b" + to [i],
433 tf = "a" + to [i] + "b" + from [i];
434 if (vertvert_map.find (ft) == vertvert_map.end () &&
435 vertvert_map.find (tf) == vertvert_map.end ())
436 {
437 vertvert_map.emplace (ft, i);
438 aggr_total [i_r] = aggr_var [i];
439 } else
440 {
441 long int where = INFINITE_INT;
442 if (vertvert_map.find (ft) != vertvert_map.end ())
443 where = vertvert_map.at (ft);
444 else if (vertvert_map.find (tf) != vertvert_map.end ())
445 where = vertvert_map.at (tf);
446 if (where == INFINITE_INT)
447 Rcpp::stop ("there is no where; this can never happen"); // # nocov
448
449 aggr_total [static_cast <R_xlen_t> (where)] += aggr_var [i];
450 }
451 }
452
453 return aggr_total;
454}