Distances on Directed Graphs in R
at main 309 lines 12 kB view raw
1#include "turn_penalty.h" 2 3/* This code adds time penalties for turning across oncoming traffic in 4 * insersections. Works by sorting all outgoing edges in a clockwise direction, 5 * and applying penalties to edges with sorted indices >= 2. (Right-side travel 6 * simply reverses indices to effectively be anti-clockwise. Incoming edges are 7 * also sorted, but sort order is not used.) Implements the following steps: 8 * 9 * 1. The `fill_edges` function creates unordered_map between centre vertices of 10 * junctions and a `std::pair <RTEdgeSet, RTEdgeSet>` of (incoming, outgoing) 11 * vertices, where `RTEdgeSet = std::set <OneEdge, clockwise_sort>`. This 12 * function also fills an unordered_set of `junction_vertices`. 13 * 2. The `replace_junctions` function scans through that map and creates new 14 * edges connecting the incoming vertex to the outgoing vertex in each 15 * direction, and applies penalties to all edges with sorted order > 2. 16 */ 17 18 19void routetimes::fill_edges (const Rcpp::DataFrame &graph, 20 std::unordered_map <std::string, 21 std::pair <RTEdgeSet, RTEdgeSet> > &the_edges, 22 std::unordered_set <std::string> &junction_vertices) 23{ 24 std::vector <std::string> vx0 = graph [".vx0"], 25 vx1 = graph [".vx1"], 26 edge_ = graph ["edge_"]; 27 std::vector <double> vx0_x = graph [".vx0_x"], 28 vx0_y = graph [".vx0_y"], 29 vx1_x = graph [".vx1_x"], 30 vx1_y = graph [".vx1_y"]; 31 32 const size_t n = static_cast <size_t> (graph.nrow ()); 33 34 for (size_t i = 0; i < n; i++) 35 { 36 OneEdge edge; 37 edge.v0 = vx0 [i]; 38 edge.v1 = vx1 [i]; 39 edge.edge = edge_ [i]; 40 edge.x = vx1_x [i] - vx0_x [i]; 41 edge.y = vx1_y [i] - vx0_y [i]; 42 43 // the incoming edge: vx0->vx1 with key = vx1 44 routetimes::replace_one_map_edge (the_edges, vx1 [i], edge, true); 45 // the outgoing edge: vx0->vx1 with key = vx0 46 routetimes::replace_one_map_edge (the_edges, vx0 [i], edge, false); 47 } 48 49 erase_non_junctions (the_edges, junction_vertices); 50} 51 52void routetimes::replace_one_map_edge ( 53 std::unordered_map <std::string, 54 std::pair <RTEdgeSet, RTEdgeSet> > &the_edges, 55 std::string key, OneEdge edge, bool incoming) 56{ 57 std::pair <RTEdgeSet, RTEdgeSet> edge_set; 58 if (the_edges.find (key) != the_edges.end ()) 59 { 60 edge_set = the_edges.at (key); 61 the_edges.erase (key); 62 } 63 if (incoming) 64 edge_set.first.emplace (edge); 65 else 66 edge_set.second.emplace (edge); 67 68 the_edges.emplace (key, edge_set); 69} 70 71/* Remove all edge items with < 3 outgoing edges. A junction has 2 or more 72 * out_edges / in_edges, but presume that flow in and out of 1->2 junctions is 73 * regulated by lights, and that only proper cross-junctions (1->3) incur 74 * additional waiting times. The result: min_edges = 4 75 */ 76void routetimes::erase_non_junctions ( 77 std::unordered_map <std::string, 78 std::pair <RTEdgeSet, RTEdgeSet> > &the_edges, 79 std::unordered_set <std::string> &junction_vertices) 80{ 81 const int min_edges = 4; 82 83 std::unordered_set <std::string> removes; 84 for (auto e: the_edges) 85 { 86 if (e.second.second.size () < min_edges) // set of outgoing edges 87 removes.emplace (e.first); 88 else 89 { 90 for (auto r: e.second.second) // RTEdgeSet 91 junction_vertices.emplace (r.v0); 92 } 93 } 94 for (auto r: removes) 95 the_edges.erase (r); 96} 97 98/* Replace junction mid-points with direct connections from in -> 0 -> out with 99 * (in, out) pairs and a binary flag for turning penalty. 100 */ 101void routetimes::replace_junctions ( 102 const std::unordered_map <std::string, 103 std::pair <RTEdgeSet, RTEdgeSet> > &the_edges, 104 std::vector <OneCompoundEdge> &junctions, 105 bool left_side) 106{ 107 // Estimate size of resultant vector - this is not simply n * (n - 1), 108 // because one way streeets mean in-edges aren't always the same as out, so 109 // numbers have to be explicitly counted. 110 size_t n_junctions = 0; 111 for (auto e: the_edges) 112 for (auto ei: e.second.first) // incoming edge 113 { 114 std::unordered_set <std::string> out_edges; 115 for (auto ej: e.second.second) // outgoing edge 116 if (ej.edge != ei.edge) 117 out_edges.emplace (ej.edge); 118 119 for (auto ej: e.second.second) 120 if (out_edges.find (ej.edge) != out_edges.end ()) 121 n_junctions++; 122 } 123 junctions.resize (n_junctions); 124 125 size_t count = 0; 126 for (auto e: the_edges) 127 { 128 // iterate over each incoming edge, and establish penalties for all 129 // other ongoing. 130 for (auto ei: e.second.first) // incoming edge 131 { 132 std::unordered_map <std::string, size_t> out_edges; 133 size_t i = 0; 134 for (auto ej: e.second.second) // outgoing edge 135 if (ej.edge != ei.edge) 136 { 137 out_edges.emplace (ej.edge, i++); 138 } 139 // For right-side travel, simply reverse the clockwise sequence of 140 // values, to give anti-clockwise sequential counts: 141 if (!left_side) 142 for (auto oe: out_edges) 143 { 144 out_edges [oe.first] = out_edges.size () - oe.second; 145 } 146 147 // Then use those out_edges to construct new edge lists: 148 for (auto ej: e.second.second) // outgoing edge 149 if (out_edges.find (ej.edge) != out_edges.end ()) 150 { 151 OneCompoundEdge edge; 152 edge.v0 = ei.v0; 153 edge.v1 = ej.v1; 154 edge.edge0 = ei.edge; 155 edge.edge1 = ej.edge; 156 edge.penalty = false; 157 if (out_edges.at (ej.edge) > 2) 158 edge.penalty = true; 159 junctions [count++] = edge; 160 } 161 } 162 } 163} 164 165// This is hard-coded for weight_streetnet.sc structure 166Rcpp::DataFrame routetimes::expand_edges (const Rcpp::DataFrame &graph, 167 std::vector <OneCompoundEdge> &junctions, int turn_penalty) 168{ 169 const size_t hash_len = 10; // for new edge IDs 170 171 std::unordered_map <std::string, double> map_x, map_y, map_d, map_dw, 172 map_time, map_timew; 173 std::unordered_map <std::string, std::string> map_object, map_highway; 174 175 std::vector <std::string> vx0 = graph [".vx0"], 176 vx1 = graph [".vx1"], 177 edge_ = graph ["edge_"], 178 object = graph ["object_"], 179 highway = graph ["highway"]; 180 std::vector <double> vx0_x = graph [".vx0_x"], 181 vx0_y = graph [".vx0_y"], 182 vx1_x = graph [".vx1_x"], 183 vx1_y = graph [".vx1_y"], 184 d = graph ["d"], 185 dw = graph ["d_weighted"], 186 time = graph ["time"], 187 timew = graph ["time_weighted"]; 188 189 std::unordered_set <std::string> edge_set; 190 for (auto j: junctions) 191 { 192 edge_set.emplace (j.edge0); 193 edge_set.emplace (j.edge1); 194 } 195 196 for (size_t i = 0; i < static_cast <size_t> (graph.nrow ()); i++) 197 { 198 if (edge_set.find (edge_ [i]) != edge_set.end ()) 199 { 200 map_x.emplace (vx0 [i], vx0_x [i]); 201 map_y.emplace (vx0 [i], vx0_y [i]); 202 map_x.emplace (vx1 [i], vx1_x [i]); 203 map_y.emplace (vx1 [i], vx1_y [i]); 204 205 map_d.emplace (edge_ [i], d [i]); 206 map_dw.emplace (edge_ [i], dw [i]); 207 map_time.emplace (edge_ [i], time [i]); 208 map_timew.emplace (edge_ [i], timew [i]); 209 210 map_object.emplace (edge_ [i], object [i]); 211 map_highway.emplace (edge_ [i], highway [i]); 212 } 213 } 214 215 const size_t n = junctions.size (); 216 std::vector <std::string> vx0_out (n), 217 vx1_out (n), 218 edge_out (n), 219 object_out (n), 220 highway_out (n), 221 old_edge_in (n), 222 old_edge_out (n); 223 std::vector <double> vx0_x_out (n), 224 vx0_y_out (n), 225 vx1_x_out (n), 226 vx1_y_out (n), 227 d_out (n), 228 dw_out (n), 229 time_out (n), 230 timew_out (n); 231 232 for (size_t i = 0; i < n; i++) 233 { 234 vx0_out [i] = junctions [i].v0; 235 vx0_x_out [i] = map_x.at (junctions [i].v0); 236 vx0_y_out [i] = map_y.at (junctions [i].v0); 237 vx1_out [i] = junctions [i].v1; 238 vx1_x_out [i] = map_x.at (junctions [i].v1); 239 vx1_y_out [i] = map_y.at (junctions [i].v1); 240 edge_out [i] = "j_" + sc::random_id (hash_len); 241 old_edge_in [i] = junctions [i].edge0; 242 old_edge_out [i] = junctions [i].edge1; 243 244 // Map all others to properties of out edge: 245 object_out [i] = map_object.at (junctions [i].edge1); 246 highway_out [i] = map_highway.at (junctions [i].edge1); 247 248 d_out [i] = map_d.at (junctions [i].edge0) + 249 map_d.at (junctions [i].edge1); 250 dw_out [i] = map_dw.at (junctions [i].edge0) + 251 map_dw.at (junctions [i].edge1); 252 time_out [i] = map_time.at (junctions [i].edge0) + 253 map_time.at (junctions [i].edge1); 254 timew_out [i] = map_timew.at (junctions [i].edge0) + 255 map_timew.at (junctions [i].edge1); 256 if (junctions [i].penalty) 257 { 258 time_out [i] += turn_penalty; 259 timew_out [i] += turn_penalty; 260 } 261 } 262 263 Rcpp::DataFrame res = Rcpp::DataFrame::create ( 264 Rcpp::Named (".vx0") = vx0_out, 265 Rcpp::Named (".vx1") = vx1_out, 266 Rcpp::Named ("edge_") = edge_out, // empty here 267 Rcpp::Named (".vx0_x") = vx0_x_out, 268 Rcpp::Named (".vx0_y") = vx0_y_out, 269 Rcpp::Named (".vx1_x") = vx1_x_out, 270 Rcpp::Named (".vx1_y") = vx1_y_out, 271 Rcpp::Named ("d") = d_out, 272 Rcpp::Named ("object_") = object_out, 273 Rcpp::Named ("highway") = highway_out, 274 Rcpp::Named ("d_weighted") = dw_out, 275 Rcpp::Named ("time") = time_out, 276 Rcpp::Named ("time_weighted") = timew_out, 277 Rcpp::Named ("old_edge_in") = old_edge_in, 278 Rcpp::Named ("old_edge_out") = old_edge_out, 279 Rcpp::_["stringsAsFactors"] = false); 280 281 return res; 282} 283 284//' rcpp_route_times 285//' 286//' @noRd 287// [[Rcpp::export]] 288Rcpp::List rcpp_route_times (const Rcpp::DataFrame graph, 289 bool left_side, int turn_penalty) 290{ 291 std::unordered_map <std::string, std::pair <RTEdgeSet, RTEdgeSet> > the_edges; 292 std::unordered_set <std::string> junction_vertices; 293 294 routetimes::fill_edges (graph, the_edges, junction_vertices); 295 std::vector <OneCompoundEdge> junctions; 296 routetimes::replace_junctions (the_edges, junctions, left_side); 297 298 Rcpp::DataFrame expanded_graph = routetimes::expand_edges (graph, 299 junctions, turn_penalty); 300 301 Rcpp::CharacterVector junction_vec (junction_vertices.size ()); 302 size_t i = 0; 303 for (auto j: junction_vertices) 304 junction_vec (i++) = j; 305 306 return Rcpp::List::create ( 307 Rcpp::Named ("graph") = expanded_graph, 308 Rcpp::Named ("junction_vertices") = junction_vec); 309}