1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
use std::cmp::Ordering;
use std::collections::{BTreeSet, BinaryHeap, HashMap, HashSet};

use geom::Duration;
use map_model::{
    connectivity, DirectedRoadID, DrivingSide, IntersectionID, Map, MovementID, PathConstraints,
    PathRequest, PathV2, TurnType,
};

use super::{ModalFilters, Neighborhood};

pub struct RatRun {
    pub shortcut_path: PathV2,
    /// May be the same as the shortcut
    pub fastest_path: PathV2,
}

/// Ideally this returns every possible path through the neighborhood between two borders. Doesn't
/// work correctly yet.
pub fn find_rat_runs(
    map: &Map,
    neighborhood: &Neighborhood,
    modal_filters: &ModalFilters,
) -> Vec<RatRun> {
    let mut results: Vec<RatRun> = Vec::new();
    for i in &neighborhood.borders {
        let mut started_from: HashSet<DirectedRoadID> = HashSet::new();
        for l in map.get_i(*i).get_outgoing_lanes(map, PathConstraints::Car) {
            let dr = map.get_l(l).get_directed_parent();
            if !started_from.contains(&dr)
                && neighborhood.orig_perimeter.interior.contains(&dr.road)
            {
                started_from.insert(dr);
                results.extend(find_rat_runs_from(
                    map,
                    dr,
                    &neighborhood.borders,
                    modal_filters,
                ));
            }
        }
    }
    results.sort_by(|a, b| a.time_ratio().partial_cmp(&b.time_ratio()).unwrap());
    results
}

fn find_rat_runs_from(
    map: &Map,
    start: DirectedRoadID,
    borders: &BTreeSet<IntersectionID>,
    modal_filters: &ModalFilters,
) -> Vec<RatRun> {
    // If there's a filter where we're starting, we can't go anywhere
    if modal_filters.roads.contains_key(&start.road) {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut back_refs = HashMap::new();
    let mut queue: BinaryHeap<Item> = BinaryHeap::new();
    queue.push(Item {
        node: start,
        cost: Duration::ZERO,
    });
    let mut visited = HashSet::new();

    while let Some(current) = queue.pop() {
        if visited.contains(&current.node) {
            continue;
        }
        visited.insert(current.node);

        // If we found a border, then stitch together the path
        let dst_i = current.node.dst_i(map);
        if borders.contains(&dst_i) {
            let mut at = current.node;
            let mut path = vec![at];
            while let Some(prev) = back_refs.get(&at).cloned() {
                path.push(prev);
                at = prev;
            }
            path.push(start);
            path.reverse();
            results.push(RatRun::new(map, path, current.cost));
            // Keep searching for more
            continue;
        }

        for mvmnt in map.get_movements_for(current.node, PathConstraints::Car) {
            // Can't cross filters
            if modal_filters.roads.contains_key(&mvmnt.to.road)
                || modal_filters
                    .intersections
                    .get(&mvmnt.parent)
                    .map(|filter| !filter.allows_turn(mvmnt.from.road, mvmnt.to.road))
                    .unwrap_or(false)
            {
                continue;
            }
            // If we've already visited the destination, don't add it again. We don't want to
            // update back_refs -- because this must be a higher-cost path to a place we've already
            // visited.
            if visited.contains(&mvmnt.to) {
                continue;
            }

            queue.push(Item {
                cost: current.cost
                    + connectivity::vehicle_cost(
                        mvmnt.from,
                        mvmnt,
                        PathConstraints::Car,
                        map.routing_params(),
                        map,
                    ),
                node: mvmnt.to,
            });
            back_refs.insert(mvmnt.to, mvmnt.from);
        }
    }

    results
}

#[derive(PartialEq, Eq)]
struct Item {
    cost: Duration,
    node: DirectedRoadID,
}
impl PartialOrd for Item {
    fn partial_cmp(&self, other: &Item) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Item {
    fn cmp(&self, other: &Item) -> Ordering {
        // BinaryHeap is a max-heap, so reverse the comparison to get smallest times first.
        let ord = other.cost.cmp(&self.cost);
        if ord != Ordering::Equal {
            return ord;
        }
        self.node.cmp(&other.node)
    }
}

impl RatRun {
    fn new(map: &Map, mut path: Vec<DirectedRoadID>, mut cost: Duration) -> RatRun {
        // The rat run starts and ends at a road just inside the neighborhood. To "motivate" using
        // the shortcut, find an entry and exit road just outside the neighborhood to calculate a
        // fastest path.
        let entry = cheap_entry(map, path[0]);
        let exit = cheap_exit(map, *path.last().unwrap());
        path.insert(0, entry.from);
        path.push(exit.to);

        // Adjust the cost for the new roads
        // TODO Or just make a PathV2 method to do this?
        cost += connectivity::vehicle_cost(
            entry.from,
            entry,
            PathConstraints::Car,
            map.routing_params(),
            map,
        );
        cost += connectivity::vehicle_cost(
            // TODO This is an abuse of vehicle_cost! It should just take the MovementID and always
            // use from... and something else should add the cost of the final road
            exit.to,
            exit,
            PathConstraints::Car,
            map.routing_params(),
            map,
        );

        let req =
            PathRequest::between_directed_roads(map, entry.from, exit.to, PathConstraints::Car)
                .unwrap();
        let shortcut_path = PathV2::from_roads(
            path,
            req.clone(),
            cost,
            // TODO We're assuming there are no uber turns. Seems unlikely in the interior of a
            // neighborhood!
            Vec::new(),
            map,
        );
        let fastest_path = map.pathfind_v2(req).unwrap();
        // TODO If the path matches up, double check the cost does too, since we may calculate it
        // differently...
        RatRun {
            shortcut_path,
            fastest_path,
        }
    }

    /// The ratio of the shortcut's time to the fastest path's time. Smaller values mean the
    /// shortcut is more desirable.
    pub fn time_ratio(&self) -> f64 {
        // TODO Not sure why yet, just avoid crashing
        if self.fastest_path.get_cost() == Duration::ZERO {
            return 1.0;
        }

        self.shortcut_path.get_cost() / self.fastest_path.get_cost()
    }
}

/// Find a movement that leads into the neighborhood at the first road in a rat-run
fn cheap_entry(map: &Map, to: DirectedRoadID) -> MovementID {
    let cheap_turn_type = if map.get_config().driving_side == DrivingSide::Right {
        TurnType::Right
    } else {
        TurnType::Left
    };
    map.get_i(to.src_i(map))
        .movements
        .values()
        .filter(|mvmnt| mvmnt.id.to == to)
        .min_by_key(|mvmnt| {
            if mvmnt.turn_type == cheap_turn_type {
                0
            } else if mvmnt.turn_type == TurnType::Straight {
                1
            } else {
                2
            }
        })
        .unwrap()
        .id
}

/// Find a movement that leads out of the neighborhood at the last road in a rat-run
fn cheap_exit(map: &Map, from: DirectedRoadID) -> MovementID {
    let cheap_turn_type = if map.get_config().driving_side == DrivingSide::Right {
        TurnType::Right
    } else {
        TurnType::Left
    };
    map.get_i(from.dst_i(map))
        .movements
        .values()
        .filter(|mvmnt| mvmnt.id.from == from)
        .min_by_key(|mvmnt| {
            if mvmnt.turn_type == cheap_turn_type {
                0
            } else if mvmnt.turn_type == TurnType::Straight {
                1
            } else {
                2
            }
        })
        .unwrap()
        .id
}