ltn/logic/
existing.rs

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

use abstutil::{Tags, Timer};
use geom::Distance;
use map_model::{osm, Crossing, FilterType, Map, RoadFilter, RoadID};

/// Edit the map, adding modal filters and crossings that're modelled in OSM in various ways.
///
/// TODO Maybe do this in the map importer pipeline!
pub fn transform_existing(map: &mut Map, timer: &mut Timer) {
    let mut edits = map.get_edits().clone();

    let mut crossings = detect_crossings(map);

    for (r, dist) in detect_filters(map) {
        edits.commands.push(map.edit_road_cmd(r, |new| {
            // If this road wasn't driveable already, then make it that way.
            if !crate::is_driveable(map.get_r(r), map) {
                // Produce a fixed [sidewalk, driving, driving, sidewalk] configuration. We could
                // get fancier and copy the tags of one of the roads we're connected to, but there
                // might be turn lanes or something extraneous there.
                let mut tags = Tags::empty();
                tags.insert("highway", "residential");
                tags.insert("lanes", "2");
                tags.insert("sidewalk", "both");
                new.lanes_ltr = osm2streets::get_lane_specs_ltr(&tags, map.get_config());
            }

            new.modal_filter = Some(RoadFilter {
                dist,
                filter_type: if map.get_bus_routes_on_road(r).is_empty() {
                    FilterType::WalkCycleOnly
                } else {
                    FilterType::BusGate
                },
            });

            new.crossings = crossings.remove(&r).unwrap_or_else(Vec::new);
        }));
    }

    // Add crossings for roads that weren't transformed above. Ideally this could just be a second
    // loop, but the EditCmd::ChangeRoad totally ovewrites the new state, so crossings would erase
    // filters unless we do it this way.
    for (r, list) in crossings {
        edits.commands.push(map.edit_road_cmd(r, |new| {
            new.crossings = list;
        }));
    }

    // Since these edits are "built-in' to the basemap, do this directly; don't call before_edit
    map.must_apply_edits(edits, timer);
    map.treat_edits_as_basemap();

    // Do not call map.keep_pathfinder_despite_edits or recalculate_pathfinding_after_edits. We
    // should NEVER use the map's built-in pathfinder in this app. If we do, crash.
}

fn detect_filters(map: &Map) -> Vec<(RoadID, Distance)> {
    let mut results = Vec::new();
    'ROAD: for r in map.all_roads() {
        // Start simple: if it's got tagged barrier nodes, use the first one of those.
        if let Some(dist) = r.barrier_nodes.get(0) {
            results.push((r.id, *dist));
            continue;
        }

        // A/B Street currently treats most footpaths as cycle-focused. Don't look at the lane
        // configuration; just look for this one tag. For example,
        // https://www.openstreetmap.org/way/392685069 is a highway=footway that is NOT a filtered
        // road.
        if !r.osm_tags.is(osm::HIGHWAY, "cycleway") {
            continue;
        }
        // A one-way cycleway is usually part of a complicated junction, like
        // https://www.openstreetmap.org/way/1002273098
        if r.osm_tags.is("oneway", "yes") {
            continue;
        }
        // Long cycleways are probably not physically driveable. Like
        // https://www.openstreetmap.org/way/174529602
        if r.length() > Distance::meters(20.0) {
            continue;
        }
        // Make sure both ends connect a driveable road, to avoid connections like
        // https://www.openstreetmap.org/way/881433973
        let mut num_degenerate = 0;
        for i in [r.src_i, r.dst_i] {
            let i = map.get_i(i);
            if !i.roads.iter().any(|r| map.get_r(*r).is_driveable()) {
                continue 'ROAD;
            }
            if i.is_degenerate() {
                num_degenerate += 1;
            }
        }
        // Make sure the OSM way was split for the no-car section by looking for a degenerate
        // intersection on at least one end. Avoid https://www.openstreetmap.org/node/4603472923,
        // but still detect https://www.openstreetmap.org/way/51538523
        if num_degenerate == 0 {
            continue;
        }

        // TODO We also modify lane types, which can modify road length, so half might be wrong. I
        // remember changing this to get around some bug...
        results.push((r.id, r.length() / 2.0));
    }
    results
}

fn detect_crossings(map: &Map) -> BTreeMap<RoadID, Vec<Crossing>> {
    let mut results = BTreeMap::new();
    for road in map.all_roads() {
        let mut list = Vec::new();
        for (dist, kind) in &road.crossing_nodes {
            list.push(Crossing {
                kind: *kind,
                dist: *dist,
            });
        }
        list.sort_by_key(|c| c.dist);
        if !list.is_empty() {
            results.insert(road.id, list);
        }
    }
    results
}