1use std::collections::{BTreeSet, HashSet};
2
3use abstutil::{Counter, Timer};
4use map_gui::tools::ColorNetwork;
5use map_model::{
6 DirectedRoadID, IntersectionID, LaneID, Map, PathConstraints, PathRequest, PathStepV2, PathV2,
7 Pathfinder, Position, RoadID,
8};
9use widgetry::GeomBatch;
10
11use crate::{App, Cell, Neighbourhood};
12
13pub struct Shortcuts {
14 pub paths: Vec<PathV2>,
15 pub count_per_road: Counter<RoadID>,
16 pub count_per_intersection: Counter<IntersectionID>,
17}
18
19impl Shortcuts {
20 pub fn empty() -> Self {
22 Self {
23 paths: Vec::new(),
24 count_per_road: Counter::new(),
25 count_per_intersection: Counter::new(),
26 }
27 }
28
29 pub fn new(map: &Map, neighbourhood: &Neighbourhood, timer: &mut Timer) -> Self {
30 let mut requests = Vec::new();
38
39 for cell in &neighbourhood.cells {
40 let entrances = find_entrances_or_exits(map, neighbourhood, cell, true);
41 let exits = find_entrances_or_exits(map, neighbourhood, cell, false);
42
43 for entrance in &entrances {
44 for exit in &exits {
45 if entrance.major_road_names != exit.major_road_names {
51 requests.push(PathRequest::vehicle(
52 Position::start(entrance.lane),
53 Position::end(exit.lane, map),
54 PathConstraints::Car,
55 ));
56 }
57 }
58 }
59 }
60
61 if requests.is_empty() {
64 return Self::empty();
65 }
66
67 let mut params = map.routing_params_respecting_modal_filters();
68
69 params.only_use_roads = neighbourhood.interior_roads.clone();
77
78 for r in &neighbourhood.interior_roads {
80 if !crate::is_driveable(map.get_r(*r), map) {
81 params.avoid_roads.insert(*r);
82 }
83 }
84
85 let pathfinder = Pathfinder::new_dijkstra(map, params, vec![PathConstraints::Car], timer);
88 let paths: Vec<PathV2> = timer
89 .parallelize(
90 "calculate paths between entrances and exits",
91 requests,
92 |req| pathfinder.pathfind_v2(req, map),
93 )
94 .into_iter()
95 .flatten()
96 .collect();
97
98 Shortcuts::from_paths(neighbourhood, paths)
105 }
106
107 pub fn from_paths(neighbourhood: &Neighbourhood, paths: Vec<PathV2>) -> Self {
108 let mut count_per_road = Counter::new();
110 let mut count_per_intersection = Counter::new();
111 for path in &paths {
112 for step in path.get_steps() {
113 match step {
114 PathStepV2::Along(dr) => {
115 if neighbourhood.interior_roads.contains(&dr.road) {
116 count_per_road.inc(dr.road);
117 }
118 }
119 PathStepV2::Movement(m) => {
120 if neighbourhood.interior_intersections.contains(&m.parent) {
121 count_per_intersection.inc(m.parent);
122 }
123 }
124 _ => unreachable!(),
126 }
127 }
128 }
129
130 Self {
131 paths,
132 count_per_road,
133 count_per_intersection,
134 }
135 }
136
137 pub fn quiet_and_total_streets(&self, neighbourhood: &Neighbourhood) -> (usize, usize) {
138 let quiet_streets = neighbourhood
139 .interior_roads
140 .iter()
141 .filter(|r| self.count_per_road.get(**r) == 0)
142 .count();
143 let total_streets = neighbourhood.interior_roads.len();
144 (quiet_streets, total_streets)
145 }
146
147 pub fn subset(&self, neighbourhood: &Neighbourhood, r: RoadID) -> Self {
148 let paths = self
149 .paths
150 .iter()
151 .filter(|path| path.crosses_road(r))
152 .cloned()
153 .collect();
154 Self::from_paths(neighbourhood, paths)
155 }
156
157 pub fn draw_heatmap(&self, app: &App) -> GeomBatch {
158 let mut colorer = ColorNetwork::no_fading(app);
159 colorer.ranked_roads(self.count_per_road.clone(), &app.cs.good_to_bad_red);
160 colorer.ranked_intersections(self.count_per_intersection.clone(), &app.cs.good_to_bad_red);
162 colorer.draw.unzoomed
163 }
164}
165
166struct EntryExit {
167 lane: LaneID,
170 major_road_names: BTreeSet<String>,
171}
172
173fn find_entrances_or_exits(
174 map: &Map,
175 neighbourhood: &Neighbourhood,
176 cell: &Cell,
177 entrances: bool,
178) -> Vec<EntryExit> {
179 let mut entry_exits = Vec::new();
180 for i in &cell.borders {
181 let major_road_names = find_major_road_names(map, neighbourhood, *i);
182 let mut seen: HashSet<DirectedRoadID> = HashSet::new();
183 let lanes = if entrances {
184 map.get_i(*i).get_outgoing_lanes(map, PathConstraints::Car)
185 } else {
186 map.get_i(*i).get_incoming_lanes(map, PathConstraints::Car)
187 };
188 for l in lanes {
189 let dr = map.get_l(l).get_directed_parent();
190 if !seen.contains(&dr) && cell.roads.contains_key(&dr.road) {
191 entry_exits.push(EntryExit {
192 lane: l,
193 major_road_names: major_road_names.clone(),
194 });
195 seen.insert(dr);
196 }
197 }
198 }
199 entry_exits
200}
201
202fn find_major_road_names(
203 map: &Map,
204 neighbourhood: &Neighbourhood,
205 i: IntersectionID,
206) -> BTreeSet<String> {
207 let mut names = BTreeSet::new();
208 for r in &map.get_i(i).roads {
209 if !neighbourhood.interior_roads.contains(r) {
210 names.insert(map.get_r(*r).get_name(None));
211 }
212 }
213 names
214}