map_model/objects/stop_signs.rs
1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4
5use abstutil::{deserialize_btreemap, serialize_btreemap};
6
7use crate::{
8 osm, Direction, DrivingSide, IntersectionID, LaneID, Map, RoadID, TurnID, TurnPriority,
9 TurnType,
10};
11
12// TODO These are old notes, they don't reflect current reality. But some of the ideas here should
13// be implemented, so keeping them...
14// 1) Pedestrians always have right-of-way. (for now -- should be toggleable later)
15// 2) Incoming roads without a stop sign have priority over roads with a sign.
16// 3) Agents with a stop sign have to actually wait some amount of time before starting the turn.
17// 4) Before starting any turn, an agent should make sure it can complete the turn without making a
18// higher-priority agent have to wait.
19// - "Complete" the turn just means the optimistic "length / max_speed" calculation -- if they
20// queue behind slow cars upstream a bit, blocking the intersection a little bit is nice and
21// realistic.
22// - The higher priority agent might not even be at the intersection yet! This'll be a little
23// harder to implement.
24// - "Higher priority" has two cases -- stop sign road always yields to a non-stop sign road. But
25// also a non-stop sign road yields to another non-stop sign road. In other words, left turns
26// yield to straight and ideally, lane-changing yields to straight too.
27// - So there still is a notion of turn priorities -- priority (can never conflict with another
28// priority), yield (can't impede a priority turn), stop (has to pause and can't impede a
29// priority or yield turn). But I don't think we want to really depict this...
30// 5) Rule 4 gives us a notion of roads that conflict -- or actually, do we even need it? No! An
31// intersection with no stop signs at all means everyone yields. An intersection with all stop
32// signs means everyone pauses before proceeding.
33// 6) Additionally, individual turns can be banned completely.
34// - Even though letting players manipulate this could make parts of the map unreachable?
35
36#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
37pub struct ControlStopSign {
38 pub id: IntersectionID,
39 /// Only roads incoming to the intersection are listed here.
40 #[serde(
41 serialize_with = "serialize_btreemap",
42 deserialize_with = "deserialize_btreemap"
43 )]
44 pub roads: BTreeMap<RoadID, RoadWithStopSign>,
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
48pub struct RoadWithStopSign {
49 pub lane_closest_to_edge: LaneID,
50 pub must_stop: bool,
51}
52
53impl ControlStopSign {
54 pub fn new(map: &Map, id: IntersectionID) -> ControlStopSign {
55 let mut ss = ControlStopSign {
56 id,
57 roads: BTreeMap::new(),
58 };
59 // One-way outbound roads don't need a stop sign, so skip them entirely.
60 for r in map.get_i(id).get_sorted_incoming_roads(map) {
61 let r = map.get_r(r);
62 let want_dir = if r.dst_i == id {
63 Direction::Fwd
64 } else {
65 Direction::Back
66 };
67 let travel_lanes: Vec<LaneID> = r
68 .lanes
69 .iter()
70 .filter_map(|l| {
71 if l.dir == want_dir && l.lane_type.is_for_moving_vehicles() {
72 Some(l.id)
73 } else {
74 None
75 }
76 })
77 .collect();
78 if !travel_lanes.is_empty() {
79 let lane_closest_to_edge = if (map.get_config().driving_side == DrivingSide::Right)
80 == (want_dir == Direction::Fwd)
81 {
82 *travel_lanes.last().unwrap()
83 } else {
84 travel_lanes[0]
85 };
86 ss.roads.insert(
87 r.id,
88 RoadWithStopSign {
89 lane_closest_to_edge,
90 must_stop: false,
91 },
92 );
93 }
94 }
95
96 // Degenerate roads and deadends don't need any stop signs. But be careful with
97 // roundabouts; we want it to be lower priority to enter a roundabout than continue through
98 // it.
99 if ss.roads.len() <= 2
100 && ss
101 .roads
102 .keys()
103 .all(|r| !map.get_r(*r).osm_tags.is("junction", "roundabout"))
104 {
105 return ss;
106 }
107 if map.get_i(id).is_cycleway(map) {
108 // Two cyclepaths intersecting can just yield.
109 return ss;
110 }
111
112 // Rank each road based on OSM highway type, and additionally:
113 // - Treat cycleways as lower priority than local roads (sad but typical reality)
114 // - Prioritize roundabouts, so they clear out faster than people enter them
115 // - Treat on/off ramps with less priority than the main part of the highway
116 // - Lower the priority of service roads
117 let mut rank: HashMap<RoadID, (osm::RoadRank, usize)> = HashMap::new();
118 for r in ss.roads.keys() {
119 let r = map.get_r(*r);
120 // Lower number is lower priority
121 let priority = if r.is_cycleway() || r.osm_tags.is(osm::HIGHWAY, "service") {
122 0
123 } else if r.osm_tags.is("junction", "roundabout") {
124 3
125 } else if r
126 .osm_tags
127 .get("highway")
128 .map(|hwy| hwy.ends_with("_link"))
129 .unwrap_or(false)
130 {
131 1
132 } else {
133 2
134 };
135 rank.insert(r.id, (r.get_rank(), priority));
136 }
137 let mut ranks = rank.values().cloned().collect::<Vec<_>>();
138 ranks.sort();
139 ranks.dedup();
140 // Highest rank is first
141 ranks.reverse();
142
143 // If all roads have the same rank, all-way stop. Otherwise, everything stops except the
144 // highest-priority roads.
145 for (r, cfg) in ss.roads.iter_mut() {
146 if ranks.len() == 1 || rank[r] != ranks[0] {
147 // Don't stop in the middle of something that's likely actually an intersection.
148 if !map.get_r(*r).is_extremely_short() {
149 cfg.must_stop = true;
150 }
151 }
152 }
153 ss
154 }
155
156 /// Get the priority of a turn according to the stop sign -- either protected or yield, never
157 /// banned.
158 pub fn get_priority(&self, turn: TurnID, map: &Map) -> TurnPriority {
159 match map.get_t(turn).turn_type {
160 TurnType::SharedSidewalkCorner => TurnPriority::Protected,
161 TurnType::Crosswalk => TurnPriority::Protected,
162 TurnType::UnmarkedCrossing => TurnPriority::Yield,
163 _ => {
164 if self.roads[&turn.src.road].must_stop {
165 TurnPriority::Yield
166 } else {
167 TurnPriority::Protected
168 }
169 }
170 }
171 }
172
173 pub fn flip_sign(&mut self, r: RoadID) {
174 let ss = self.roads.get_mut(&r).unwrap();
175 ss.must_stop = !ss.must_stop;
176 }
177}