ltn/logic/
existing.rs

1use std::collections::BTreeMap;
2
3use abstutil::{Tags, Timer};
4use geom::Distance;
5use map_model::{osm, Crossing, FilterType, Map, RoadFilter, RoadID};
6
7/// Edit the map, adding modal filters and crossings that're modelled in OSM in various ways.
8///
9/// TODO Maybe do this in the map importer pipeline!
10pub fn transform_existing(map: &mut Map, timer: &mut Timer) {
11    let mut edits = map.get_edits().clone();
12
13    let mut crossings = detect_crossings(map);
14
15    for (r, dist) in detect_filters(map) {
16        edits.commands.push(map.edit_road_cmd(r, |new| {
17            // If this road wasn't driveable already, then make it that way.
18            if !crate::is_driveable(map.get_r(r), map) {
19                // Produce a fixed [sidewalk, driving, driving, sidewalk] configuration. We could
20                // get fancier and copy the tags of one of the roads we're connected to, but there
21                // might be turn lanes or something extraneous there.
22                let mut tags = Tags::empty();
23                tags.insert("highway", "residential");
24                tags.insert("lanes", "2");
25                tags.insert("sidewalk", "both");
26                new.lanes_ltr = osm2streets::get_lane_specs_ltr(&tags, map.get_config());
27            }
28
29            new.modal_filter = Some(RoadFilter {
30                dist,
31                filter_type: if map.get_bus_routes_on_road(r).is_empty() {
32                    FilterType::WalkCycleOnly
33                } else {
34                    FilterType::BusGate
35                },
36            });
37
38            new.crossings = crossings.remove(&r).unwrap_or_else(Vec::new);
39        }));
40    }
41
42    // Add crossings for roads that weren't transformed above. Ideally this could just be a second
43    // loop, but the EditCmd::ChangeRoad totally ovewrites the new state, so crossings would erase
44    // filters unless we do it this way.
45    for (r, list) in crossings {
46        edits.commands.push(map.edit_road_cmd(r, |new| {
47            new.crossings = list;
48        }));
49    }
50
51    // Since these edits are "built-in' to the basemap, do this directly; don't call before_edit
52    map.must_apply_edits(edits, timer);
53    map.treat_edits_as_basemap();
54
55    // Do not call map.keep_pathfinder_despite_edits or recalculate_pathfinding_after_edits. We
56    // should NEVER use the map's built-in pathfinder in this app. If we do, crash.
57}
58
59fn detect_filters(map: &Map) -> Vec<(RoadID, Distance)> {
60    let mut results = Vec::new();
61    'ROAD: for r in map.all_roads() {
62        // Start simple: if it's got tagged barrier nodes, use the first one of those.
63        if let Some(dist) = r.barrier_nodes.get(0) {
64            results.push((r.id, *dist));
65            continue;
66        }
67
68        // A/B Street currently treats most footpaths as cycle-focused. Don't look at the lane
69        // configuration; just look for this one tag. For example,
70        // https://www.openstreetmap.org/way/392685069 is a highway=footway that is NOT a filtered
71        // road.
72        if !r.osm_tags.is(osm::HIGHWAY, "cycleway") {
73            continue;
74        }
75        // A one-way cycleway is usually part of a complicated junction, like
76        // https://www.openstreetmap.org/way/1002273098
77        if r.osm_tags.is("oneway", "yes") {
78            continue;
79        }
80        // Long cycleways are probably not physically driveable. Like
81        // https://www.openstreetmap.org/way/174529602
82        if r.length() > Distance::meters(20.0) {
83            continue;
84        }
85        // Make sure both ends connect a driveable road, to avoid connections like
86        // https://www.openstreetmap.org/way/881433973
87        let mut num_degenerate = 0;
88        for i in [r.src_i, r.dst_i] {
89            let i = map.get_i(i);
90            if !i.roads.iter().any(|r| map.get_r(*r).is_driveable()) {
91                continue 'ROAD;
92            }
93            if i.is_degenerate() {
94                num_degenerate += 1;
95            }
96        }
97        // Make sure the OSM way was split for the no-car section by looking for a degenerate
98        // intersection on at least one end. Avoid https://www.openstreetmap.org/node/4603472923,
99        // but still detect https://www.openstreetmap.org/way/51538523
100        if num_degenerate == 0 {
101            continue;
102        }
103
104        // TODO We also modify lane types, which can modify road length, so half might be wrong. I
105        // remember changing this to get around some bug...
106        results.push((r.id, r.length() / 2.0));
107    }
108    results
109}
110
111fn detect_crossings(map: &Map) -> BTreeMap<RoadID, Vec<Crossing>> {
112    let mut results = BTreeMap::new();
113    for road in map.all_roads() {
114        let mut list = Vec::new();
115        for (dist, kind) in &road.crossing_nodes {
116            list.push(Crossing {
117                kind: *kind,
118                dist: *dist,
119            });
120        }
121        list.sort_by_key(|c| c.dist);
122        if !list.is_empty() {
123            results.insert(road.id, list);
124        }
125    }
126    results
127}