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
use std::collections::BTreeSet;

use abstutil::{MultiMap, Timer};

use crate::{osm, OriginalRoad, RawMap};

/// Some roads might be totally disconnected from the largest clump because of how the map's
/// bounding polygon was drawn, or bad map data, or which roads are filtered from OSM. Remove them.
pub fn remove_disconnected_roads(map: &mut RawMap, timer: &mut Timer) {
    timer.start("removing disconnected roads");
    // This is a simple floodfill, not Tarjan's. Assumes all roads bidirectional.
    // All the usizes are indices into the original list of roads

    let mut next_roads: MultiMap<osm::NodeID, OriginalRoad> = MultiMap::new();
    for id in map.roads.keys() {
        next_roads.insert(id.i1, *id);
        next_roads.insert(id.i2, *id);
    }

    let mut partitions: Vec<Vec<OriginalRoad>> = Vec::new();
    let mut unvisited_roads: BTreeSet<OriginalRoad> = map
        .roads
        .iter()
        .filter_map(|(id, r)| if r.is_light_rail() { None } else { Some(*id) })
        .collect();

    while !unvisited_roads.is_empty() {
        let mut queue_roads: Vec<OriginalRoad> = vec![*unvisited_roads.iter().next().unwrap()];
        let mut current_partition: Vec<OriginalRoad> = Vec::new();
        while !queue_roads.is_empty() {
            let current = queue_roads.pop().unwrap();
            if !unvisited_roads.contains(&current) {
                continue;
            }
            unvisited_roads.remove(&current);
            current_partition.push(current);

            for other_r in next_roads.get(current.i1).iter() {
                queue_roads.push(*other_r);
            }
            for other_r in next_roads.get(current.i2).iter() {
                queue_roads.push(*other_r);
            }
        }
        partitions.push(current_partition);
    }

    partitions.sort_by_key(|roads| roads.len());
    partitions.reverse();
    for p in partitions.iter().skip(1) {
        for id in p {
            info!("Removing {} because it's disconnected from most roads", id);
            map.roads.remove(id).unwrap();
            next_roads.remove(id.i1, *id);
            next_roads.remove(id.i2, *id);
        }
    }

    // Also remove cul-de-sacs here. TODO Support them properly, but for now, they mess up parking
    // hint matching (loop PolyLine) and pathfinding later.
    map.roads.retain(|id, _| id.i1 != id.i2);

    // Remove intersections without any roads
    map.intersections
        .retain(|id, _| !next_roads.get(*id).is_empty());
    timer.stop("removing disconnected roads");
}