map_model/pathfind/
walking.rs

1//! Pathfinding for pedestrians, as well as figuring out if somebody should use public transit.
2
3use std::collections::HashMap;
4
5use fast_paths::InputGraph;
6use serde::{Deserialize, Serialize};
7
8use geom::{Distance, Duration};
9
10use crate::pathfind::engine::{CreateEngine, PathfindEngine};
11use crate::pathfind::node_map::{deserialize_nodemap, NodeMap};
12use crate::pathfind::vehicles::VehiclePathfinder;
13use crate::pathfind::zone_cost;
14use crate::pathfind::{round, unround};
15use crate::{
16    DirectedRoadID, IntersectionID, Map, PathConstraints, PathRequest, PathStep, PathStepV2,
17    PathV2, Position, TransitRoute, TransitRouteID, TransitStopID, TurnType,
18};
19
20#[derive(Clone, Serialize, Deserialize)]
21pub struct SidewalkPathfinder {
22    #[serde(deserialize_with = "deserialize_nodemap")]
23    nodes: NodeMap<WalkingNode>,
24    use_transit: bool,
25    engine: PathfindEngine,
26}
27
28#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)]
29pub enum WalkingNode {
30    /// false is src_i, true is dst_i
31    SidewalkEndpoint(DirectedRoadID, bool),
32    // TODO Lots of complexity below could be avoided by explicitly sticking TransitRouteID here too.
33    // Worth it?
34    RideTransit(TransitStopID),
35    LeaveMap(IntersectionID),
36}
37
38impl WalkingNode {
39    pub fn closest(pos: Position, map: &Map) -> WalkingNode {
40        let lane = map.get_l(pos.lane());
41        let dst_i = lane.length() - pos.dist_along() <= pos.dist_along();
42        WalkingNode::SidewalkEndpoint(lane.get_directed_parent(), dst_i)
43    }
44
45    fn end_transit(pos: Position, map: &Map) -> WalkingNode {
46        let l = map.get_l(pos.lane());
47        if map.get_i(l.src_i).is_outgoing_border() && pos.dist_along() == Distance::ZERO {
48            return WalkingNode::LeaveMap(l.src_i);
49        }
50        if map.get_i(l.dst_i).is_outgoing_border() && pos.dist_along() == l.length() {
51            return WalkingNode::LeaveMap(l.dst_i);
52        }
53        WalkingNode::closest(pos, map)
54    }
55}
56
57impl SidewalkPathfinder {
58    pub fn empty() -> SidewalkPathfinder {
59        SidewalkPathfinder {
60            nodes: NodeMap::new(),
61            use_transit: false,
62            engine: PathfindEngine::Empty,
63        }
64    }
65
66    pub fn new(
67        map: &Map,
68        use_transit: Option<(&VehiclePathfinder, &VehiclePathfinder)>,
69        engine: &CreateEngine,
70    ) -> SidewalkPathfinder {
71        let mut nodes = NodeMap::new();
72        for r in map.all_roads() {
73            // Regardless of whether the road has sidewalks/shoulders on one or both sides, add
74            // both. These could change later, and we want the node IDs to match up.
75            for dr in r.id.both_directions() {
76                for endpt in [true, false] {
77                    nodes.get_or_insert(WalkingNode::SidewalkEndpoint(dr, endpt));
78                }
79            }
80        }
81        if use_transit.is_some() {
82            // Add a node for each stop.
83            for ts in map.all_transit_stops().keys() {
84                nodes.get_or_insert(WalkingNode::RideTransit(*ts));
85            }
86            for i in map.all_outgoing_borders() {
87                // We could filter for those with sidewalks, but eh
88                nodes.get_or_insert(WalkingNode::LeaveMap(i.id));
89            }
90        }
91
92        let input_graph = make_input_graph(&nodes, use_transit, map);
93        let engine = engine.create(input_graph);
94
95        SidewalkPathfinder {
96            nodes,
97            use_transit: use_transit.is_some(),
98            engine,
99        }
100    }
101
102    pub fn apply_edits(
103        &mut self,
104        map: &Map,
105        use_transit: Option<(&VehiclePathfinder, &VehiclePathfinder)>,
106    ) {
107        if matches!(self.engine, PathfindEngine::Empty) {
108            return;
109        }
110
111        let input_graph = make_input_graph(&self.nodes, use_transit, map);
112        let engine = self.engine.reuse_ordering().create(input_graph);
113        self.engine = engine;
114    }
115
116    pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<PathV2> {
117        if matches!(self.engine, PathfindEngine::Empty) {
118            return None;
119        }
120
121        if req.start.lane() == req.end.lane() {
122            return Some(one_step_walking_path(req, map));
123        }
124        let (raw_weight, raw_nodes) = self.engine.calculate_path(
125            self.nodes.get(WalkingNode::closest(req.start, map)),
126            self.nodes.get(WalkingNode::closest(req.end, map)),
127        )?;
128        let nodes: Vec<WalkingNode> = raw_nodes
129            .into_iter()
130            .map(|id| self.nodes.translate_id(id))
131            .collect();
132        let steps = walking_path_to_steps(nodes, map);
133        let cost = unround(raw_weight);
134        Some(PathV2::new(map, steps, req, cost, Vec::new()))
135    }
136
137    /// Attempt the pathfinding and see if we should ride public transit. If so, says (stop1,
138    /// optional stop 2, route). If there's no stop 2, then ride transit off the border.
139    pub fn should_use_transit(
140        &self,
141        map: &Map,
142        start: Position,
143        end: Position,
144    ) -> Option<(TransitStopID, Option<TransitStopID>, TransitRouteID)> {
145        if matches!(self.engine, PathfindEngine::Empty) {
146            return None;
147        }
148
149        assert!(self.use_transit);
150
151        let (_, raw_nodes) = self.engine.calculate_path(
152            self.nodes.get(WalkingNode::closest(start, map)),
153            self.nodes.get(WalkingNode::end_transit(end, map)),
154        )?;
155        let nodes: Vec<WalkingNode> = raw_nodes
156            .into_iter()
157            .map(|id| self.nodes.translate_id(id))
158            .collect();
159
160        if false {
161            println!("should_use_transit from {} to {}?", start, end);
162            for n in &nodes {
163                println!("- {:?}", n);
164            }
165        }
166
167        let mut first_stop = None;
168        let mut last_stop = None;
169        let mut possible_routes: Vec<&TransitRoute> = Vec::new();
170        for n in &nodes {
171            match n {
172                WalkingNode::RideTransit(stop2) => {
173                    if let Some(stop1) = first_stop {
174                        // Keep riding the same route?
175                        // We need to do this check, because some transfers might be instantaneous
176                        // at the same stop and involve no walking.
177                        // Also need to make sure the stops are in the proper order. We might have
178                        // a transfer, then try to hop on the first route again, but starting from
179                        // a different point.
180                        let mut filtered = possible_routes.clone();
181                        filtered.retain(|r| {
182                            let idx1 = r.stops.iter().position(|s| *s == stop1).unwrap();
183                            let idx2 = r.stops.iter().position(|s| s == stop2);
184                            idx2.map(|idx2| idx1 < idx2).unwrap_or(false)
185                        });
186                        if filtered.is_empty() {
187                            // Aha, a transfer!
188                            return Some((
189                                first_stop.unwrap(),
190                                // TODO I thought this should be impossible, but huge_seattle hits
191                                // it. Workaround for now by just walking.
192                                Some(last_stop?),
193                                possible_routes[0].id,
194                            ));
195                        }
196                        last_stop = Some(*stop2);
197                        possible_routes = filtered;
198                    } else {
199                        first_stop = Some(*stop2);
200                        possible_routes = map.get_routes_serving_stop(*stop2);
201                        assert!(!possible_routes.is_empty());
202                    }
203                }
204                WalkingNode::LeaveMap(i) => {
205                    // Make sure the route actually leaves via the correct border!
206                    if let Some(r) = possible_routes.iter().find(|r| {
207                        r.end_border
208                            .map(|l| map.get_l(l).dst_i == *i)
209                            .unwrap_or(false)
210                    }) {
211                        return Some((first_stop.unwrap(), None, r.id));
212                    }
213                    // We can get close to the border, but should hop off at some stop.
214                    return Some((
215                        first_stop.unwrap(),
216                        Some(last_stop.expect("impossible transit transfer")),
217                        possible_routes[0].id,
218                    ));
219                }
220                WalkingNode::SidewalkEndpoint(_, _) => {
221                    if let Some(stop1) = first_stop {
222                        return Some((
223                            stop1,
224                            Some(last_stop.expect("impossible transit transfer")),
225                            possible_routes[0].id,
226                        ));
227                    }
228                }
229            }
230        }
231        None
232    }
233
234    pub fn all_costs_from(&self, start: Position, map: &Map) -> HashMap<DirectedRoadID, Duration> {
235        if matches!(self.engine, PathfindEngine::Empty) {
236            return HashMap::new();
237        }
238
239        let start = self.nodes.get(WalkingNode::closest(start, map));
240        let raw_costs = if self.engine.is_dijkstra() {
241            self.engine.all_costs_from(start)
242        } else {
243            // The CH engine doesn't support this!
244            let input_graph = make_input_graph(&self.nodes, None, map);
245            CreateEngine::Dijkstra
246                .create(input_graph)
247                .all_costs_from(start)
248        };
249        raw_costs
250            .into_iter()
251            .filter_map(|(k, v)| {
252                // If we want to be more precise here, maybe take the min or max here of both
253                // endpoints
254                if let WalkingNode::SidewalkEndpoint(dr, _) = self.nodes.translate_id(k) {
255                    Some((dr, unround(v)))
256                } else {
257                    None
258                }
259            })
260            .collect()
261    }
262}
263
264fn make_input_graph(
265    nodes: &NodeMap<WalkingNode>,
266    use_transit: Option<(&VehiclePathfinder, &VehiclePathfinder)>,
267    map: &Map,
268) -> InputGraph {
269    let max_speed = Some(crate::MAX_WALKING_SPEED);
270    let mut input_graph = InputGraph::new();
271
272    for l in map.all_lanes() {
273        if l.is_walkable() {
274            // Sidewalks can be crossed in two directions. When there's a steep incline, of course
275            // it flips.
276            let n1 = nodes.get(WalkingNode::SidewalkEndpoint(
277                l.get_directed_parent(),
278                false,
279            ));
280            let n2 = nodes.get(WalkingNode::SidewalkEndpoint(l.get_directed_parent(), true));
281
282            for (step, pair) in [
283                (PathStep::Lane(l.id), (n1, n2)),
284                (PathStep::ContraflowLane(l.id), (n2, n1)),
285            ] {
286                let mut cost =
287                    l.length() / step.max_speed_along(max_speed, PathConstraints::Pedestrian, map);
288                // TODO Tune this penalty, along with many others.
289                if l.is_shoulder() {
290                    cost = 2.0 * cost;
291                }
292                input_graph.add_edge(pair.0, pair.1, round(cost));
293            }
294        }
295    }
296
297    for t in map.all_turns() {
298        if t.between_sidewalks() {
299            let src = map.get_l(t.id.src);
300            let dst = map.get_l(t.id.dst);
301            let from = nodes.get(WalkingNode::SidewalkEndpoint(
302                src.get_directed_parent(),
303                src.dst_i == t.id.parent,
304            ));
305            let to = nodes.get(WalkingNode::SidewalkEndpoint(
306                dst.get_directed_parent(),
307                dst.dst_i == t.id.parent,
308            ));
309
310            let mut cost = t.geom.length()
311                / PathStep::Turn(t.id).max_speed_along(max_speed, PathConstraints::Pedestrian, map)
312                + zone_cost(t.id.to_movement(map), PathConstraints::Pedestrian, map);
313
314            if t.turn_type == TurnType::UnmarkedCrossing {
315                // TODO Add to RoutingParams
316                cost = 3.0 * cost;
317            }
318
319            input_graph.add_edge(from, to, round(cost));
320            input_graph.add_edge(to, from, round(cost));
321        }
322    }
323
324    if let Some(graphs) = use_transit {
325        transit_input_graph(&mut input_graph, nodes, map, graphs.0, graphs.1);
326    }
327
328    nodes.guarantee_node_ordering(&mut input_graph);
329    input_graph.freeze();
330    input_graph
331}
332
333fn transit_input_graph(
334    input_graph: &mut InputGraph,
335    nodes: &NodeMap<WalkingNode>,
336    map: &Map,
337    bus_graph: &VehiclePathfinder,
338    train_graph: &VehiclePathfinder,
339) {
340    let max_speed = Some(crate::MAX_WALKING_SPEED);
341    // Connect stops with both sidewalk endpoints, using the appropriate distance.
342    for stop in map.all_transit_stops().values() {
343        let ride_transit = nodes.get(WalkingNode::RideTransit(stop.id));
344        let lane = map.get_l(stop.sidewalk_pos.lane());
345        for (endpt, step) in [
346            (false, PathStep::Lane(lane.id)),
347            (true, PathStep::ContraflowLane(lane.id)),
348        ] {
349            let dist = if endpt {
350                lane.length() - stop.sidewalk_pos.dist_along()
351            } else {
352                stop.sidewalk_pos.dist_along()
353            };
354            let cost = dist / step.max_speed_along(max_speed, PathConstraints::Pedestrian, map);
355            // Add some extra penalty to using a stop. Otherwise a path might try to pass through
356            // it uselessly.
357            let penalty = Duration::seconds(10.0);
358            let sidewalk = nodes.get(WalkingNode::SidewalkEndpoint(
359                lane.get_directed_parent(),
360                endpt,
361            ));
362            input_graph.add_edge(sidewalk, ride_transit, round(cost + penalty));
363            input_graph.add_edge(ride_transit, sidewalk, round(cost + penalty));
364        }
365    }
366
367    // Connect each adjacent stop along a route, with the cost based on how long it'll take a
368    // transit vehicle to drive between the stops. Optimistically assume no waiting time at a stop.
369    for route in map.all_transit_routes() {
370        // TODO Also plug in border starts
371        for pair in route.stops.windows(2) {
372            let (stop1, stop2) = (map.get_ts(pair[0]), map.get_ts(pair[1]));
373            let req = PathRequest::vehicle(stop1.driving_pos, stop2.driving_pos, route.route_type);
374            let maybe_driving_cost = match route.route_type {
375                PathConstraints::Bus => bus_graph.pathfind(req, map).map(|p| p.get_cost()),
376                PathConstraints::Train => train_graph.pathfind(req, map).map(|p| p.get_cost()),
377                _ => unreachable!(),
378            };
379            if let Some(driving_cost) = maybe_driving_cost {
380                input_graph.add_edge(
381                    nodes.get(WalkingNode::RideTransit(stop1.id)),
382                    nodes.get(WalkingNode::RideTransit(stop2.id)),
383                    round(driving_cost),
384                );
385            } else {
386                panic!(
387                    "No transit route from {} to {} now for {}! Prevent this edit",
388                    stop1.driving_pos, stop2.driving_pos, route.long_name,
389                );
390            }
391        }
392
393        if let Some(l) = route.end_border {
394            let stop1 = map.get_ts(*route.stops.last().unwrap());
395            let req =
396                PathRequest::vehicle(stop1.driving_pos, Position::end(l, map), route.route_type);
397            let maybe_driving_cost = match route.route_type {
398                PathConstraints::Bus => bus_graph.pathfind(req, map).map(|p| p.get_cost()),
399                PathConstraints::Train => train_graph.pathfind(req, map).map(|p| p.get_cost()),
400                _ => unreachable!(),
401            };
402            if let Some(driving_cost) = maybe_driving_cost {
403                let border = map.get_i(map.get_l(l).dst_i);
404                input_graph.add_edge(
405                    nodes.get(WalkingNode::RideTransit(stop1.id)),
406                    nodes.get(WalkingNode::LeaveMap(border.id)),
407                    round(driving_cost),
408                );
409            } else {
410                panic!(
411                    "No transit route from {} to end of {} now for {}! Prevent this edit",
412                    stop1.driving_pos, l, route.long_name,
413                );
414            }
415        }
416    }
417}
418
419// TODO Fold into reconstruct_path?
420fn walking_path_to_steps(path: Vec<WalkingNode>, map: &Map) -> Vec<PathStepV2> {
421    let mut steps = Vec::new();
422
423    for pair in path.windows(2) {
424        let (r1, r1_endpt) = match pair[0] {
425            WalkingNode::SidewalkEndpoint(r, endpt) => (r, endpt),
426            WalkingNode::RideTransit(_) => unreachable!(),
427            WalkingNode::LeaveMap(_) => unreachable!(),
428        };
429        let r2 = match pair[1] {
430            WalkingNode::SidewalkEndpoint(r, _) => r,
431            WalkingNode::RideTransit(_) => unreachable!(),
432            WalkingNode::LeaveMap(_) => unreachable!(),
433        };
434
435        if r1 == r2 {
436            if r1_endpt {
437                steps.push(PathStepV2::Contraflow(r1));
438            } else {
439                steps.push(PathStepV2::Along(r1));
440            }
441        } else {
442            let i = if r1_endpt {
443                r1.dst_i(map)
444            } else {
445                r1.src_i(map)
446            };
447            // Could assert the intersection matches (r2, r2_endpt).
448            if let Some(t) =
449                map.get_turn_between(r1.must_get_sidewalk(map), r2.must_get_sidewalk(map), i)
450            {
451                steps.push(PathStepV2::Movement(t.id.to_movement(map)));
452            } else if let Some(t) =
453                map.get_turn_between(r2.must_get_sidewalk(map), r1.must_get_sidewalk(map), i)
454            {
455                steps.push(PathStepV2::ContraflowMovement(t.id.to_movement(map)));
456            } else {
457                println!("walking_path_to_steps has a weird path:");
458                for s in &path {
459                    println!("- {:?}", s);
460                }
461                panic!(
462                    "No turn from {} ({}) to {} ({}) at {}",
463                    r1,
464                    r1.must_get_sidewalk(map),
465                    r2,
466                    r2.must_get_sidewalk(map),
467                    i
468                );
469            }
470        }
471    }
472
473    // Don't start or end a path in a turn; sim layer breaks.
474    if let PathStepV2::Movement(mvmnt) | PathStepV2::ContraflowMovement(mvmnt) = steps[0] {
475        let lane = match steps[0] {
476            PathStepV2::Movement(m) => m.from,
477            PathStepV2::ContraflowMovement(m) => m.to,
478            _ => unreachable!(),
479        };
480        if lane.src_i(map) == mvmnt.parent {
481            steps.insert(0, PathStepV2::Contraflow(lane));
482        } else {
483            steps.insert(0, PathStepV2::Along(lane));
484        }
485    }
486    if let PathStepV2::Movement(mvmnt) | PathStepV2::ContraflowMovement(mvmnt) =
487        steps.last().cloned().unwrap()
488    {
489        let lane = match steps.last().unwrap() {
490            PathStepV2::Movement(m) => m.to,
491            PathStepV2::ContraflowMovement(m) => m.from,
492            _ => unreachable!(),
493        };
494        if lane.src_i(map) == mvmnt.parent {
495            steps.push(PathStepV2::Along(lane));
496        } else {
497            steps.push(PathStepV2::Contraflow(lane));
498        }
499    }
500
501    steps
502}
503
504// TODO Do we even need this at all?
505fn one_step_walking_path(req: PathRequest, map: &Map) -> PathV2 {
506    let l = req.start.lane();
507    // Weird case, but it can happen for walking from a building path to a stop that're actually at
508    // the same spot.
509    let (step_v2, step_v1) = if req.start.dist_along() <= req.end.dist_along() {
510        (
511            PathStepV2::Along(map.get_l(l).get_directed_parent()),
512            PathStep::Lane(l),
513        )
514    } else {
515        (
516            PathStepV2::Contraflow(map.get_l(l).get_directed_parent()),
517            PathStep::ContraflowLane(l),
518        )
519    };
520    let mut cost = (req.start.dist_along() - req.end.dist_along()).abs()
521        / step_v1.max_speed_along(
522            Some(crate::MAX_WALKING_SPEED),
523            PathConstraints::Pedestrian,
524            map,
525        );
526    if map.get_l(l).is_shoulder() {
527        cost = 2.0 * cost;
528    }
529    PathV2::new(map, vec![step_v2], req, cost, Vec::new())
530}