map_model/pathfind/
v1.rs

1use std::collections::{BTreeMap, VecDeque};
2use std::fmt;
3
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7use abstutil::prettyprint_usize;
8use geom::{Distance, Duration, PolyLine, Polygon, Ring, Speed, EPSILON_DIST};
9
10use crate::{
11    BuildingID, DirectedRoadID, LaneID, Map, PathConstraints, Position, RoadID, Traversable,
12    TurnID, UberTurn,
13};
14
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub enum PathStep {
17    /// Original direction
18    Lane(LaneID),
19    /// Sidewalks only!
20    ContraflowLane(LaneID),
21    Turn(TurnID),
22    ContraflowTurn(TurnID),
23}
24
25impl PathStep {
26    pub fn as_traversable(&self) -> Traversable {
27        match self {
28            PathStep::Lane(id) => Traversable::Lane(*id),
29            PathStep::ContraflowLane(id) => Traversable::Lane(*id),
30            PathStep::Turn(id) => Traversable::Turn(*id),
31            PathStep::ContraflowTurn(id) => Traversable::Turn(*id),
32        }
33    }
34
35    pub fn as_lane(&self) -> LaneID {
36        self.as_traversable().as_lane()
37    }
38
39    pub fn as_turn(&self) -> TurnID {
40        self.as_traversable().as_turn()
41    }
42
43    // start is relative to the start of the actual geometry -- so from the lane's real start for
44    // ContraflowLane.
45    fn exact_slice(
46        &self,
47        map: &Map,
48        start: Distance,
49        dist_ahead: Option<Distance>,
50    ) -> Result<PolyLine> {
51        if let Some(d) = dist_ahead {
52            if d < Distance::ZERO {
53                panic!("Negative dist_ahead?! {}", d);
54            }
55            if d == Distance::ZERO {
56                bail!("0 dist ahead for slice");
57            }
58        }
59
60        match self {
61            PathStep::Lane(id) => {
62                let pts = &map.get_l(*id).lane_center_pts;
63                if let Some(d) = dist_ahead {
64                    pts.maybe_exact_slice(start, start + d)
65                } else {
66                    pts.maybe_exact_slice(start, pts.length())
67                }
68            }
69            PathStep::ContraflowLane(id) => {
70                let pts = map.get_l(*id).lane_center_pts.reversed();
71                let reversed_start = pts.length() - start;
72                if let Some(d) = dist_ahead {
73                    pts.maybe_exact_slice(reversed_start, reversed_start + d)
74                } else {
75                    pts.maybe_exact_slice(reversed_start, pts.length())
76                }
77            }
78            PathStep::Turn(id) => {
79                let pts = &map.get_t(*id).geom;
80                if let Some(d) = dist_ahead {
81                    pts.maybe_exact_slice(start, start + d)
82                } else {
83                    pts.maybe_exact_slice(start, pts.length())
84                }
85            }
86            PathStep::ContraflowTurn(id) => {
87                let pts = &map.get_t(*id).geom.reversed();
88                let reversed_start = pts.length() - start;
89                if let Some(d) = dist_ahead {
90                    pts.maybe_exact_slice(reversed_start, reversed_start + d)
91                } else {
92                    pts.maybe_exact_slice(reversed_start, pts.length())
93                }
94            }
95        }
96    }
97
98    /// The single definitive place to determine how fast somebody could go along a single road or
99    /// turn. This should be used for pathfinding and simulation.
100    pub fn max_speed_along(
101        &self,
102        max_speed_on_flat_ground: Option<Speed>,
103        constraints: PathConstraints,
104        map: &Map,
105    ) -> Speed {
106        self.max_speed_and_incline_along(max_speed_on_flat_ground, constraints, map)
107            .0
108    }
109
110    /// The single definitive place to determine how fast somebody could go along a single road or
111    /// turn. This should be used for pathfinding and simulation. Returns (speed, percent incline).
112    pub fn max_speed_and_incline_along(
113        &self,
114        max_speed_on_flat_ground: Option<Speed>,
115        constraints: PathConstraints,
116        map: &Map,
117    ) -> (Speed, f64) {
118        match self {
119            PathStep::Lane(l) => Traversable::max_speed_along_road(
120                map.get_l(*l).get_directed_parent(),
121                max_speed_on_flat_ground,
122                constraints,
123                map,
124            ),
125            PathStep::ContraflowLane(l) => Traversable::max_speed_along_road(
126                {
127                    let mut dr = map.get_l(*l).get_directed_parent();
128                    dr.dir = dr.dir.opposite();
129                    dr
130                },
131                max_speed_on_flat_ground,
132                constraints,
133                map,
134            ),
135            PathStep::Turn(t) | PathStep::ContraflowTurn(t) => (
136                Traversable::max_speed_along_movement(
137                    t.to_movement(map),
138                    max_speed_on_flat_ground,
139                    constraints,
140                    map,
141                ),
142                0.0,
143            ),
144        }
145    }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
149pub struct Path {
150    steps: VecDeque<PathStep>,
151    // The original request used to produce this path. Calling shift(), add(), modify_step(), etc
152    // will NOT affect this.
153    orig_req: PathRequest,
154
155    // Also track progress along the original path.
156    total_length: Distance,
157    crossed_so_far: Distance,
158
159    // A list of uber-turns encountered by this path, in order. The steps are flattened into the
160    // sequence of turn->lane->...->turn.
161    uber_turns: VecDeque<UberTurn>,
162    // Is the current_step in the middle of an UberTurn?
163    currently_inside_ut: Option<UberTurn>,
164
165    blocked_starts: Vec<LaneID>,
166}
167
168impl Path {
169    pub(crate) fn new(
170        map: &Map,
171        steps: Vec<PathStep>,
172        orig_req: PathRequest,
173        uber_turns: Vec<UberTurn>,
174        blocked_starts: Vec<LaneID>,
175    ) -> Path {
176        // Haven't seen problems here in a very long time. Noticeably saves some time to skip.
177        if false {
178            validate_continuity(map, &steps);
179        }
180        if false {
181            validate_restrictions(map, &steps);
182        }
183        if false {
184            validate_zones(map, &steps, &orig_req);
185        }
186        let mut path = Path {
187            steps: VecDeque::from(steps),
188            orig_req,
189            total_length: Distance::ZERO,
190            crossed_so_far: Distance::ZERO,
191            uber_turns: uber_turns.into_iter().collect(),
192            currently_inside_ut: None,
193            blocked_starts,
194        };
195        for step in &path.steps {
196            path.total_length += path.dist_crossed_from_step(map, step);
197        }
198        path
199    }
200
201    /// Once we finish this PathStep, how much distance will be crossed? If the step is at the
202    /// beginning or end of our path, then the full length may not be used.
203    pub fn dist_crossed_from_step(&self, map: &Map, step: &PathStep) -> Distance {
204        match step {
205            PathStep::Lane(l) => {
206                let lane = map.get_l(*l);
207                if self.orig_req.start.lane() == lane.id {
208                    lane.length() - self.orig_req.start.dist_along()
209                } else if self.orig_req.end.lane() == lane.id {
210                    self.orig_req.end.dist_along()
211                } else {
212                    lane.length()
213                }
214            }
215            PathStep::ContraflowLane(l) => {
216                let lane = map.get_l(*l);
217                if self.orig_req.start.lane() == lane.id {
218                    self.orig_req.start.dist_along()
219                } else if self.orig_req.end.lane() == lane.id {
220                    lane.length() - self.orig_req.end.dist_along()
221                } else {
222                    lane.length()
223                }
224            }
225            PathStep::Turn(t) | PathStep::ContraflowTurn(t) => map.get_t(*t).geom.length(),
226        }
227    }
228
229    /// The original PathRequest used to produce this path. If the path has been modified since
230    /// creation, the start and end of the request won't match up with the current path steps.
231    pub fn get_req(&self) -> &PathRequest {
232        &self.orig_req
233    }
234
235    pub fn crossed_so_far(&self) -> Distance {
236        self.crossed_so_far
237    }
238
239    pub fn total_length(&self) -> Distance {
240        self.total_length
241    }
242
243    pub fn percent_dist_crossed(&self) -> f64 {
244        // Sometimes this happens
245        if self.total_length == Distance::ZERO {
246            return 1.0;
247        }
248        self.crossed_so_far / self.total_length
249    }
250
251    pub fn is_empty(&self) -> bool {
252        self.steps.is_empty()
253    }
254
255    pub fn is_last_step(&self) -> bool {
256        self.steps.len() == 1
257    }
258
259    pub fn isnt_last_step(&self) -> bool {
260        self.steps.len() > 1
261    }
262
263    pub fn currently_inside_ut(&self) -> &Option<UberTurn> {
264        &self.currently_inside_ut
265    }
266    pub fn about_to_start_ut(&self) -> Option<&UberTurn> {
267        if self.steps.len() < 2 || self.uber_turns.is_empty() {
268            return None;
269        }
270        if let PathStep::Turn(t) = self.steps[1] {
271            if self.uber_turns[0].path[0] == t {
272                return Some(&self.uber_turns[0]);
273            }
274        }
275        None
276    }
277
278    pub fn shift(&mut self, map: &Map) -> PathStep {
279        let step = self.steps.pop_front().unwrap();
280        self.crossed_so_far += self.dist_crossed_from_step(map, &step);
281
282        #[allow(clippy::collapsible_if)] // better readability
283        if let Some(ref ut) = self.currently_inside_ut {
284            if step == PathStep::Turn(*ut.path.last().unwrap()) {
285                self.currently_inside_ut = None;
286            }
287        } else if !self.steps.is_empty() && !self.uber_turns.is_empty() {
288            if self.steps[0] == PathStep::Turn(self.uber_turns[0].path[0]) {
289                self.currently_inside_ut = Some(self.uber_turns.pop_front().unwrap());
290            }
291        }
292
293        if self.steps.len() == 1 {
294            // TODO When handle_uber_turns experiment is turned off, this will crash
295            assert!(self.uber_turns.is_empty());
296            assert!(self.currently_inside_ut.is_none());
297        }
298
299        step
300    }
301
302    pub fn add(&mut self, step: PathStep, map: &Map) {
303        if let Some(PathStep::Lane(l)) = self.steps.back() {
304            if *l == self.orig_req.end.lane() {
305                self.total_length += map.get_l(*l).length() - self.orig_req.end.dist_along();
306            }
307        }
308        // TODO We assume we'll be going along the full length of this new step
309        self.total_length += step.as_traversable().get_polyline(map).length();
310
311        self.steps.push_back(step);
312        // TODO Maybe need to amend uber_turns?
313    }
314
315    pub fn is_upcoming_uber_turn_component(&self, t: TurnID) -> bool {
316        self.uber_turns
317            .front()
318            .map(|ut| ut.path.contains(&t))
319            .unwrap_or(false)
320    }
321
322    /// Trusting the caller to do this in valid ways.
323    pub fn modify_step(&mut self, idx: usize, step: PathStep, map: &Map) {
324        assert!(self.currently_inside_ut.is_none());
325        // We're assuming this step was in the middle of the path, meaning we were planning to
326        // travel its full length
327        self.total_length -= self.steps[idx].as_traversable().get_polyline(map).length();
328
329        // When replacing a turn, also update any references to it in uber_turns
330        if let PathStep::Turn(old_turn) = self.steps[idx] {
331            for uts in &mut self.uber_turns {
332                if let Some(turn_idx) = uts.path.iter().position(|i| i == &old_turn) {
333                    if let PathStep::Turn(new_turn) = step {
334                        uts.path[turn_idx] = new_turn;
335                    } else {
336                        panic!("expected turn, but found {:?}", step);
337                    }
338                }
339            }
340        }
341
342        self.steps[idx] = step;
343        self.total_length += self.steps[idx].as_traversable().get_polyline(map).length();
344
345        if self.total_length < Distance::ZERO {
346            panic!(
347                "modify_step broke total_length, it's now {}",
348                self.total_length
349            );
350        }
351    }
352
353    pub fn current_step(&self) -> PathStep {
354        self.steps[0]
355    }
356
357    pub fn next_step(&self) -> PathStep {
358        self.steps[1]
359    }
360    pub fn maybe_next_step(&self) -> Option<PathStep> {
361        if self.is_last_step() {
362            None
363        } else {
364            Some(self.next_step())
365        }
366    }
367
368    pub fn last_step(&self) -> PathStep {
369        self.steps[self.steps.len() - 1]
370    }
371
372    /// Traces along the path from its originally requested start. This is only valid to call for
373    /// an umodified path.
374    ///
375    /// It mostly seems the PolyLine's length will match `total_length`, but callers beware if
376    /// you're relying on this -- check walking paths with the buggy sharp angles particularly.
377    pub fn trace(&self, map: &Map) -> Option<PolyLine> {
378        let t1 = self.steps[0].as_traversable();
379        let t2 = Traversable::Lane(self.orig_req.start.lane());
380        if t1 != t2 {
381            warn!(
382                "Can't trace modified path; first step is {}, but requested started from {}",
383                t1, t2
384            );
385            return None;
386        }
387        self.trace_from_start(map, self.orig_req.start.dist_along())
388    }
389
390    /// Traces along the path from a specified distance along the first step until the end.
391    pub fn trace_from_start(&self, map: &Map, start_dist: Distance) -> Option<PolyLine> {
392        let orig_end_dist = self.orig_req.end.dist_along();
393
394        if self.steps.len() == 1 {
395            let dist_ahead = if start_dist < orig_end_dist {
396                orig_end_dist - start_dist
397            } else {
398                start_dist - orig_end_dist
399            };
400
401            // Why might this fail? It's possible there are paths on their last step that're
402            // effectively empty, because they're a 0-length turn, or something like a pedestrian
403            // crossing a front path and immediately getting on a bike.
404            return self.steps[0]
405                .exact_slice(map, start_dist, Some(dist_ahead))
406                .ok();
407        }
408
409        let mut pts_so_far: Option<PolyLine> = None;
410
411        // Special case the first step with start_dist.
412        if let Ok(pts) = self.steps[0].exact_slice(map, start_dist, None) {
413            pts_so_far = Some(pts);
414        }
415
416        // Crunch through the intermediate steps, as long as we can.
417        for i in 1..self.steps.len() {
418            // Restrict the last step's slice
419            let dist_ahead = if i == self.steps.len() - 1 {
420                Some(match self.steps[i] {
421                    PathStep::ContraflowLane(l) => {
422                        map.get_l(l).lane_center_pts.reversed().length() - orig_end_dist
423                    }
424                    PathStep::ContraflowTurn(t) => {
425                        map.get_t(t).geom.reversed().length() - orig_end_dist
426                    }
427                    _ => orig_end_dist,
428                })
429            } else {
430                None
431            };
432
433            let start_dist_this_step = match self.steps[i] {
434                // TODO Length of a PolyLine can slightly change when points are reversed! That
435                // seems bad.
436                PathStep::ContraflowLane(l) => map.get_l(l).lane_center_pts.reversed().length(),
437                PathStep::ContraflowTurn(t) => map.get_t(t).geom.reversed().length(),
438                _ => Distance::ZERO,
439            };
440            if let Ok(new_pts) = self.steps[i].exact_slice(map, start_dist_this_step, dist_ahead) {
441                if pts_so_far.is_some() {
442                    match pts_so_far.unwrap().extend(new_pts) {
443                        Ok(new) => {
444                            pts_so_far = Some(new);
445                        }
446                        Err(err) => {
447                            println!("WARNING: Couldn't trace some path: {}", err);
448                            return None;
449                        }
450                    }
451                } else {
452                    pts_so_far = Some(new_pts);
453                }
454            }
455        }
456
457        Some(pts_so_far.unwrap())
458    }
459
460    /// Draws the thickened path, matching entire roads. Ignores the path's exact starting and
461    /// ending distance.
462    pub fn trace_v2(&self, map: &Map) -> Result<Polygon> {
463        let mut left_pts = Vec::new();
464        let mut right_pts = Vec::new();
465        for step in &self.steps {
466            match step {
467                PathStep::Lane(l) => {
468                    let road = map.get_parent(*l);
469                    let width = road.get_half_width();
470                    if map.get_l(*l).dst_i == road.dst_i {
471                        left_pts.extend(road.center_pts.shift_left(width)?.into_points());
472                        right_pts.extend(road.center_pts.shift_right(width)?.into_points());
473                    } else {
474                        left_pts
475                            .extend(road.center_pts.shift_right(width)?.reversed().into_points());
476                        right_pts
477                            .extend(road.center_pts.shift_left(width)?.reversed().into_points());
478                    }
479                }
480                PathStep::ContraflowLane(_) => todo!(),
481                // Just make a straight line across the intersection. It'd be fancier to try and
482                // trace along.
483                PathStep::Turn(_) | PathStep::ContraflowTurn(_) => {}
484            }
485        }
486        right_pts.reverse();
487        left_pts.extend(right_pts);
488        left_pts.push(left_pts[0]);
489        Ok(Ring::deduping_new(left_pts)?.into_polygon())
490    }
491
492    pub fn get_steps(&self) -> &VecDeque<PathStep> {
493        &self.steps
494    }
495
496    /// Estimate how long following the path will take in the best case, assuming no traffic or
497    /// delay at intersections. To determine the speed along each step, the agent's optional
498    /// max_speed must be known.
499    pub fn estimate_duration(&self, map: &Map, max_speed: Option<Speed>) -> Duration {
500        let mut total = Duration::ZERO;
501        for step in &self.steps {
502            let dist = self.dist_crossed_from_step(map, step);
503            let speed = step.max_speed_along(max_speed, self.orig_req.constraints, map);
504            total += dist / speed;
505        }
506        total
507    }
508
509    /// If the agent following this path will initially block some intermediate lanes as they move
510    /// between a driveway and `get_req().start`, then record them here.
511    pub fn get_blocked_starts(&self) -> Vec<LaneID> {
512        self.blocked_starts.clone()
513    }
514
515    /// Returns the total elevation (gain, loss) experienced over the path.
516    pub fn get_total_elevation_change(&self, map: &Map) -> (Distance, Distance) {
517        let mut gain = Distance::ZERO;
518        let mut loss = Distance::ZERO;
519        for step in &self.steps {
520            let (from, to) = match step {
521                PathStep::Lane(l) => {
522                    let lane = map.get_l(*l);
523                    (
524                        map.get_i(lane.src_i).elevation,
525                        map.get_i(lane.dst_i).elevation,
526                    )
527                }
528                PathStep::ContraflowLane(l) => {
529                    let lane = map.get_l(*l);
530                    (
531                        map.get_i(lane.dst_i).elevation,
532                        map.get_i(lane.src_i).elevation,
533                    )
534                }
535                PathStep::Turn(_) | PathStep::ContraflowTurn(_) => {
536                    continue;
537                }
538            };
539            if from < to {
540                gain += to - from;
541            } else {
542                loss += from - to;
543            }
544        }
545        (gain, loss)
546    }
547
548    pub fn get_step_at_dist_along(&self, map: &Map, mut dist_along: Distance) -> Result<PathStep> {
549        for step in &self.steps {
550            let dist_here = self.dist_crossed_from_step(map, step);
551            if dist_along <= dist_here {
552                return Ok(*step);
553            }
554            dist_along -= dist_here;
555        }
556        bail!(
557            "get_step_at_dist_along has leftover distance of {}",
558            dist_along
559        );
560    }
561
562    pub fn crosses_road(&self, r: RoadID) -> bool {
563        for step in &self.steps {
564            if let PathStep::Lane(l) | PathStep::ContraflowLane(l) = step {
565                if l.road == r {
566                    return true;
567                }
568            }
569        }
570        false
571    }
572}
573
574#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
575pub struct PathRequest {
576    pub start: Position,
577    pub end: Position,
578    pub constraints: PathConstraints,
579    // If present, also consider this start position, adding an extra cost to use it. When a Path
580    // is returned, 'start' might be switched to use this one instead, reflecting the choice made.
581    // TODO It's assumed this lane is on the same directed road as `start`, but this isn't
582    // enforced!
583    pub(crate) alt_start: Option<(Position, Duration)>,
584}
585
586impl fmt::Display for PathRequest {
587    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
588        write!(
589            f,
590            "PathRequest({} along {}... to {} along {} for {:?})",
591            self.start.dist_along(),
592            self.start.lane(),
593            self.end.dist_along(),
594            self.end.lane(),
595            self.constraints,
596        )
597    }
598}
599
600impl PathRequest {
601    /// Determines the start and end position to travel between two buildings for a certain mode.
602    /// The path won't cover modality transfers -- if somebody has to walk between the building and
603    /// a parking spot or bikeable position, that won't be captured here.
604    pub fn between_buildings(
605        map: &Map,
606        from: BuildingID,
607        to: BuildingID,
608        constraints: PathConstraints,
609    ) -> Option<PathRequest> {
610        let from = map.get_b(from);
611        let to = map.get_b(to);
612        let (start, end) = match constraints {
613            PathConstraints::Pedestrian => (from.sidewalk_pos, to.sidewalk_pos),
614            PathConstraints::Bike => (from.biking_connection(map)?.0, to.biking_connection(map)?.0),
615            PathConstraints::Car => (
616                from.driving_connection(map)?.0,
617                to.driving_connection(map)?.0,
618            ),
619            // These two aren't useful here. A pedestrian using transit would pass in
620            // PathConstraints::Pedestrian. There's no reason yet to find a route for a bus or
621            // train to travel between buildings.
622            PathConstraints::Bus | PathConstraints::Train => unimplemented!(),
623        };
624        if constraints == PathConstraints::Car {
625            Some(PathRequest::leave_from_driveway(
626                start,
627                end,
628                constraints,
629                map,
630            ))
631        } else {
632            Some(PathRequest {
633                start,
634                end,
635                constraints,
636                alt_start: None,
637            })
638        }
639    }
640
641    /// The caller must pass in two valid sidewalk positions. This isn't verified.
642    pub fn walking(start: Position, end: Position) -> PathRequest {
643        PathRequest {
644            start,
645            end,
646            constraints: PathConstraints::Pedestrian,
647            alt_start: None,
648        }
649    }
650
651    /// The caller must pass in two valid positions for the vehicle type. This isn't verified. No
652    /// off-side turns from driveways happen; the exact start position is used.
653    pub fn vehicle(start: Position, end: Position, constraints: PathConstraints) -> PathRequest {
654        PathRequest {
655            start,
656            end,
657            constraints,
658            alt_start: None,
659        }
660    }
661
662    /// The caller must pass in two valid positions for the vehicle type. This isn't verified.
663    /// TODO The vehicle may cut exit the driveway onto the off-side of the road.
664    pub fn leave_from_driveway(
665        start: Position,
666        end: Position,
667        constraints: PathConstraints,
668        map: &Map,
669    ) -> PathRequest {
670        let alt_start = (|| {
671            let start_lane = map.get_l(start.lane());
672            let road = map.get_r(start_lane.id.road);
673            // If start and end road match, don't exit offside
674            // TODO Sometimes this is valid! Just not if we're trying to go behind ourselves
675            if road.id == end.lane().road {
676                return None;
677            }
678            let offside_dir = start_lane.dir.opposite();
679            let alt_lane = road.find_closest_lane(start_lane.id, |l| {
680                l.dir == offside_dir && constraints.can_use(l, map)
681            })?;
682            // TODO Do we need buffer_dist like driving_connection does?
683            let pos = start.equiv_pos(alt_lane, map);
684            let number_lanes_between =
685                ((start_lane.id.offset as f64) - (alt_lane.offset as f64)).abs();
686            // TODO Tune the cost of cutting across lanes
687            let cost = Duration::seconds(10.0) * number_lanes_between;
688            Some((pos, cost))
689        })();
690        PathRequest {
691            start,
692            end,
693            constraints,
694            alt_start,
695        }
696    }
697
698    /// Create a request from the beginning of one road to the end of another. Picks an arbitrary
699    /// start and end lane from the available ones.
700    pub fn between_directed_roads(
701        map: &Map,
702        from: DirectedRoadID,
703        to: DirectedRoadID,
704        constraints: PathConstraints,
705    ) -> Option<PathRequest> {
706        let start = Position::start(from.lanes(constraints, map).pop()?);
707        let end = Position::end(to.lanes(constraints, map).pop()?, map);
708        Some(PathRequest {
709            start,
710            end,
711            constraints,
712            alt_start: None,
713        })
714    }
715
716    /// Group similar requests together, returning the number of matches. This can be used to
717    /// calculate less paths and multiply whatever's being measured by the count.
718    ///
719    /// Note this throws away detail. It only groups by the mode and from/to parent. Exact position
720    /// and alternate starting points are lost.
721    pub fn deduplicate(map: &Map, requests: Vec<PathRequest>) -> Vec<(PathRequest, usize)> {
722        let count_before = requests.len();
723        let mut common: BTreeMap<
724            (PathConstraints, DirectedRoadID, DirectedRoadID),
725            (PathRequest, usize),
726        > = BTreeMap::new();
727        for req in requests {
728            let key = (
729                req.constraints,
730                map.get_l(req.start.lane()).get_directed_parent(),
731                map.get_l(req.end.lane()).get_directed_parent(),
732            );
733            let pair = common.entry(key).or_insert_with(|| (req, 0));
734            pair.1 += 1;
735        }
736        if false {
737            info!(
738                "{} requests deduplicated down to {}",
739                prettyprint_usize(count_before),
740                prettyprint_usize(common.len())
741            );
742        }
743        common.into_values().collect()
744    }
745}
746
747fn validate_continuity(map: &Map, steps: &[PathStep]) {
748    if steps.is_empty() {
749        panic!("Empty path");
750    }
751    for pair in steps.windows(2) {
752        let from = match pair[0] {
753            PathStep::Lane(id) => map.get_l(id).last_pt(),
754            PathStep::ContraflowLane(id) => map.get_l(id).first_pt(),
755            PathStep::Turn(id) => map.get_t(id).geom.last_pt(),
756            PathStep::ContraflowTurn(id) => map.get_t(id).geom.first_pt(),
757        };
758        let to = match pair[1] {
759            PathStep::Lane(id) => map.get_l(id).first_pt(),
760            PathStep::ContraflowLane(id) => map.get_l(id).last_pt(),
761            PathStep::Turn(id) => map.get_t(id).geom.first_pt(),
762            PathStep::ContraflowTurn(id) => map.get_t(id).geom.last_pt(),
763        };
764        let len = from.dist_to(to);
765        if len > EPSILON_DIST {
766            println!("All steps in invalid path:");
767            for s in steps {
768                match s {
769                    PathStep::Lane(l) => println!(
770                        "  {:?} from {} to {}",
771                        s,
772                        map.get_l(*l).src_i,
773                        map.get_l(*l).dst_i
774                    ),
775                    PathStep::ContraflowLane(l) => println!(
776                        "  {:?} from {} to {}",
777                        s,
778                        map.get_l(*l).dst_i,
779                        map.get_l(*l).src_i
780                    ),
781                    PathStep::Turn(_) | PathStep::ContraflowTurn(_) => println!("  {:?}", s),
782                }
783            }
784            panic!(
785                "pathfind() returned path that warps {} from {:?} to {:?}",
786                len, pair[0], pair[1]
787            );
788        }
789    }
790}
791
792fn validate_restrictions(map: &Map, steps: &[PathStep]) {
793    for triple in steps.windows(5) {
794        if let (PathStep::Lane(l1), PathStep::Lane(l2), PathStep::Lane(l3)) =
795            (triple[0], triple[2], triple[4])
796        {
797            let from = map.get_parent(l1);
798            let via = l2.road;
799            let to = l3.road;
800
801            for (dont_via, dont_to) in &from.complicated_turn_restrictions {
802                if via == *dont_via && to == *dont_to {
803                    panic!(
804                        "Some path does illegal uber-turn: {} -> {} -> {}",
805                        l1, l2, l3
806                    );
807                }
808            }
809        }
810    }
811}
812
813fn validate_zones(map: &Map, steps: &[PathStep], req: &PathRequest) {
814    let z1 = map.get_parent(req.start.lane()).get_zone(map);
815    let z2 = map.get_parent(req.end.lane()).get_zone(map);
816
817    for step in steps {
818        if let PathStep::Turn(t) | PathStep::ContraflowTurn(t) = step {
819            if map
820                .get_parent(t.src)
821                .access_restrictions
822                .allow_through_traffic
823                .contains(req.constraints)
824                && !map
825                    .get_parent(t.dst)
826                    .access_restrictions
827                    .allow_through_traffic
828                    .contains(req.constraints)
829            {
830                // Entering our destination zone is fine
831                let into_zone = map.get_parent(t.dst).get_zone(map);
832                if into_zone != z1 && into_zone != z2 {
833                    // TODO There are lots of false positive here that occur when part of the graph
834                    // is separated from the rest by access-restricted roads. Could maybe detect
835                    // that here, or ideally even extend the zone at map construction time (or edit
836                    // time) when that happens.
837                    panic!("{} causes illegal entrance into a zone at {}", req, t);
838                }
839            }
840        }
841    }
842}