map_model/pathfind/
vehicles.rs

1//! Pathfinding for cars, bikes, buses, and trains using contraction hierarchies
2
3use std::collections::HashMap;
4
5use fast_paths::InputGraph;
6use serde::{Deserialize, Serialize};
7
8use abstutil::MultiMap;
9use geom::Duration;
10
11use crate::pathfind::engine::{CreateEngine, PathfindEngine};
12use crate::pathfind::node_map::{deserialize_nodemap, NodeMap};
13use crate::pathfind::uber_turns::{IntersectionCluster, UberTurnV2};
14use crate::pathfind::zone_cost;
15use crate::pathfind::{round, unround};
16use crate::{
17    osm, DirectedRoadID, Direction, LaneType, Map, MovementID, PathConstraints, PathRequest,
18    PathV2, Position, RoutingParams, Traversable,
19};
20
21#[derive(Clone, Serialize, Deserialize)]
22pub struct VehiclePathfinder {
23    #[serde(deserialize_with = "deserialize_nodemap")]
24    nodes: NodeMap<Node>,
25    uber_turns: Vec<UberTurnV2>,
26    constraints: PathConstraints,
27    params: RoutingParams,
28    pub engine: PathfindEngine,
29}
30
31#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
32pub enum Node {
33    Road(DirectedRoadID),
34    UberTurn(usize),
35}
36
37impl VehiclePathfinder {
38    pub fn empty() -> VehiclePathfinder {
39        VehiclePathfinder {
40            nodes: NodeMap::new(),
41            uber_turns: Vec::new(),
42            constraints: PathConstraints::Car,
43            params: RoutingParams::default(),
44            engine: PathfindEngine::Empty,
45        }
46    }
47
48    pub fn new(
49        map: &Map,
50        constraints: PathConstraints,
51        params: &RoutingParams,
52        engine: &CreateEngine,
53    ) -> VehiclePathfinder {
54        // Insert every road as a node.
55        let mut nodes = NodeMap::new();
56        for r in map.all_roads() {
57            // Regardless of current lane types or even directions, add both. These could change
58            // later, and we want the node IDs to match up.
59            nodes.get_or_insert(Node::Road(DirectedRoadID {
60                road: r.id,
61                dir: Direction::Fwd,
62            }));
63            nodes.get_or_insert(Node::Road(DirectedRoadID {
64                road: r.id,
65                dir: Direction::Back,
66            }));
67        }
68
69        // Find all uber-turns and make a node for them too.
70        let mut uber_turns = Vec::new();
71        for ic in IntersectionCluster::find_all(map) {
72            for ut in ic.into_v2(map) {
73                nodes.get_or_insert(Node::UberTurn(uber_turns.len()));
74                uber_turns.push(ut);
75            }
76        }
77
78        let input_graph = make_input_graph(constraints, &nodes, &uber_turns, params, map);
79        let engine = engine.create(input_graph);
80
81        VehiclePathfinder {
82            nodes,
83            uber_turns,
84            constraints,
85            params: params.clone(),
86            engine,
87        }
88    }
89
90    pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<PathV2> {
91        if matches!(self.engine, PathfindEngine::Empty) {
92            return None;
93        }
94
95        assert!(!map.get_l(req.start.lane()).is_walkable());
96        let mut starts = vec![(
97            self.nodes.get(Node::Road(
98                map.get_l(req.start.lane()).get_directed_parent(),
99            )),
100            0,
101        )];
102        if let Some((pos, cost)) = req.alt_start {
103            starts.push((
104                self.nodes
105                    .get(Node::Road(map.get_l(pos.lane()).get_directed_parent())),
106                round(cost),
107            ));
108        }
109        let (raw_weight, raw_nodes) = self.engine.calculate_path_multiple_sources_and_targets(
110            starts,
111            vec![(
112                self.nodes
113                    .get(Node::Road(map.get_l(req.end.lane()).get_directed_parent())),
114                0,
115            )],
116        )?;
117
118        let mut road_steps = Vec::new();
119        let mut uber_turns = Vec::new();
120        for node in raw_nodes.into_iter().map(|id| self.nodes.translate_id(id)) {
121            match node {
122                Node::Road(dr) => {
123                    road_steps.push(dr);
124                }
125                Node::UberTurn(ut) => {
126                    // Flatten the uber-turn into the roads it crosses.
127                    for mvmnt in &self.uber_turns[ut].path {
128                        road_steps.push(mvmnt.to);
129                    }
130                    road_steps.pop();
131                    // Also remember the uber-turn exists.
132                    uber_turns.push(self.uber_turns[ut].clone());
133                }
134            }
135        }
136        let cost = unround(raw_weight);
137        Some(PathV2::from_roads(road_steps, req, cost, uber_turns, map))
138    }
139
140    pub fn apply_edits(&mut self, map: &Map) {
141        if matches!(self.engine, PathfindEngine::Empty) {
142            return;
143        }
144
145        // The NodeMap is just all roads and uber-turns -- it won't change. So we can also reuse
146        // the node ordering.
147        // TODO Make sure the result of this is deterministic and equivalent to computing from
148        // scratch.
149        let input_graph = make_input_graph(
150            self.constraints,
151            &self.nodes,
152            &self.uber_turns,
153            &self.params,
154            map,
155        );
156        let engine = self.engine.reuse_ordering().create(input_graph);
157        self.engine = engine;
158    }
159
160    pub fn all_costs_from(&self, start: Position, map: &Map) -> HashMap<DirectedRoadID, Duration> {
161        if matches!(self.engine, PathfindEngine::Empty) {
162            return HashMap::new();
163        }
164
165        let start = self
166            .nodes
167            .get(Node::Road(map.get_l(start.lane()).get_directed_parent()));
168        let raw_costs = if self.engine.is_dijkstra() {
169            self.engine.all_costs_from(start)
170        } else {
171            // The CH engine doesn't support this!
172            let input_graph = make_input_graph(
173                self.constraints,
174                &self.nodes,
175                &self.uber_turns,
176                &self.params,
177                map,
178            );
179            CreateEngine::Dijkstra
180                .create(input_graph)
181                .all_costs_from(start)
182        };
183        raw_costs
184            .into_iter()
185            .filter_map(|(k, v)| {
186                if let Node::Road(dr) = self.nodes.translate_id(k) {
187                    Some((dr, unround(v)))
188                } else {
189                    None
190                }
191            })
192            .collect()
193    }
194}
195
196fn make_input_graph(
197    constraints: PathConstraints,
198    nodes: &NodeMap<Node>,
199    uber_turns: &[UberTurnV2],
200    params: &RoutingParams,
201    map: &Map,
202) -> InputGraph {
203    let mut input_graph = InputGraph::new();
204
205    // From some roads, instead of adding edges to movements, add edges to these (indexed)
206    // uber-turns.
207    let mut uber_turn_entrances: MultiMap<DirectedRoadID, usize> = MultiMap::new();
208    for (idx, ut) in uber_turns.iter().enumerate() {
209        // Force the nodes to always match up in the graph for different vehicle types.
210        nodes.get(Node::UberTurn(idx));
211
212        // But actually, make sure this uber-turn only contains roads that can be used by this
213        // vehicle.
214        // TODO Need to test editing lanes inside an IntersectionCluster very carefully. See Mercer
215        // and Dexter.
216        if ut
217            .path
218            .iter()
219            .all(|mvmnt| !mvmnt.to.lanes(constraints, map).is_empty())
220        {
221            uber_turn_entrances.insert(ut.entry(), idx);
222        }
223    }
224
225    let roads_to_consider = if params.only_use_roads.is_empty() {
226        map.all_roads().iter().collect::<Vec<_>>()
227    } else {
228        params
229            .only_use_roads
230            .iter()
231            .map(|r| map.get_r(*r))
232            .collect()
233    };
234
235    for r in roads_to_consider {
236        for dr in r.id.both_directions() {
237            let from = nodes.get(Node::Road(dr));
238            if !dr.lanes(constraints, map).is_empty() {
239                let indices = uber_turn_entrances.get(dr);
240                if indices.is_empty() {
241                    for mvmnt in map.get_movements_for(dr, constraints) {
242                        if let Some(cost) =
243                            vehicle_cost(mvmnt.from, mvmnt, constraints, params, map)
244                        {
245                            input_graph.add_edge(
246                                from,
247                                nodes.get(Node::Road(mvmnt.to)),
248                                round(cost),
249                            );
250                        }
251                    }
252                } else {
253                    for idx in indices {
254                        let ut = &uber_turns[*idx];
255
256                        let mut sum_cost = Duration::ZERO;
257                        let mut ok = true;
258                        for mvmnt in &ut.path {
259                            if let Some(cost) =
260                                vehicle_cost(mvmnt.from, *mvmnt, constraints, params, map)
261                            {
262                                sum_cost += cost;
263                            } else {
264                                // A vehicle isn't allowed to do one of the steps inside the
265                                // uber-turn. So just toss out the entire uber-turn from the graph.
266                                ok = false;
267                                break;
268                            }
269                        }
270                        if ok {
271                            input_graph.add_edge(
272                                from,
273                                nodes.get(Node::UberTurn(*idx)),
274                                round(sum_cost),
275                            );
276                            input_graph.add_edge(
277                                nodes.get(Node::UberTurn(*idx)),
278                                nodes.get(Node::Road(ut.exit())),
279                                // The cost is already captured for entering the uber-turn
280                                1,
281                            );
282                        }
283                    }
284                }
285            }
286        }
287    }
288
289    nodes.guarantee_node_ordering(&mut input_graph);
290    input_graph.freeze();
291    input_graph
292}
293
294/// This returns the pathfinding cost of crossing one road and turn, in units of time. It factors
295/// in the ideal time to cross the space and penalties for entering an access-restricted zone,
296/// taking an unprotected turn, or going up a steep hill for some vehicle types. If this returns
297/// `None`, then the movement isn't actually allowed.
298pub fn vehicle_cost(
299    dr: DirectedRoadID,
300    mvmnt: MovementID,
301    constraints: PathConstraints,
302    params: &RoutingParams,
303    map: &Map,
304) -> Option<Duration> {
305    if params.avoid_roads.contains(&dr.road)
306        || params
307            .avoid_movements_between
308            .contains(&(mvmnt.from.road, mvmnt.to.road))
309    {
310        return None;
311    }
312
313    let road = map.get_r(dr.road);
314    let movement = &map.get_i(mvmnt.parent).movements[&mvmnt];
315    let max_speed = match constraints {
316        PathConstraints::Car | PathConstraints::Bus | PathConstraints::Train => None,
317        PathConstraints::Bike => Some(crate::MAX_BIKE_SPEED),
318        PathConstraints::Pedestrian => unreachable!(),
319    };
320    let t1 = road.length() / Traversable::max_speed_along_road(dr, max_speed, constraints, map).0;
321
322    let t2 = movement.geom.length()
323        / Traversable::max_speed_along_movement(mvmnt, max_speed, constraints, map);
324
325    let base = match constraints {
326        PathConstraints::Car | PathConstraints::Train => t1 + t2,
327        PathConstraints::Bike => {
328            // TODO If we're on a driving lane, higher speed limit is worse.
329            // TODO Bike lanes next to parking is dangerous.
330            // TODO Prefer bike lanes, then bus lanes, then driving lanes. For now, express that by
331            // multiplying the base cost.
332            let lt_penalty = if dr.has_lanes(LaneType::Biking, map) {
333                params.bike_lane_penalty
334            } else if dr.has_lanes(LaneType::Bus, map) {
335                params.bus_lane_penalty
336            } else {
337                params.driving_lane_penalty
338            };
339
340            lt_penalty * (t1 + t2)
341        }
342        PathConstraints::Bus => {
343            // Like Car, but prefer bus lanes.
344            let lt_penalty = if dr.has_lanes(LaneType::Bus, map) {
345                1.0
346            } else {
347                1.1
348            };
349            lt_penalty * (t1 + t2)
350        }
351        PathConstraints::Pedestrian => unreachable!(),
352    };
353
354    let mut multiplier = 1.0;
355    if constraints == PathConstraints::Bike
356        && (params.avoid_steep_incline_penalty - 1.0).abs() > f64::EPSILON
357    {
358        let percent_incline = if dr.dir == Direction::Fwd {
359            road.percent_incline
360        } else {
361            -road.percent_incline
362        };
363        if percent_incline >= 0.08 {
364            multiplier *= params.avoid_steep_incline_penalty;
365        }
366    }
367
368    if constraints == PathConstraints::Bike
369        && (params.avoid_high_stress - 1.0).abs() > f64::EPSILON
370        && road.high_stress_for_bikes(map, dr.dir)
371    {
372        multiplier *= params.avoid_high_stress;
373    }
374
375    let mut extra = zone_cost(mvmnt, constraints, map);
376    // Penalize unprotected turns at a stop sign from smaller to larger roads.
377    if map.is_unprotected_turn(dr.road, mvmnt.to.road, movement.turn_type) {
378        extra += params.unprotected_turn_penalty
379    }
380
381    if (params.main_road_penalty - 1.0).abs() > f64::EPSILON
382        && road.get_rank() != osm::RoadRank::Local
383    {
384        multiplier *= params.main_road_penalty;
385    }
386
387    Some(multiplier * base + extra)
388}