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}