map_model/objects/
traffic_signals.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5
6use geom::{Distance, Duration, Speed};
7
8use crate::edits::perma_traffic_signal;
9use crate::make::traffic_signals::get_possible_policies;
10use crate::{
11    Intersection, IntersectionID, Map, Movement, MovementID, RoadID, TurnID, TurnPriority,
12};
13
14// The pace to use for crosswalk pace in m/s
15// https://en.wikipedia.org/wiki/Preferred_walking_speed
16const CROSSWALK_PACE: Speed = Speed::const_meters_per_second(1.4);
17
18/// A traffic signal consists of a sequence of Stages that repeat in a cycle. Most Stages last for a
19/// fixed duration. During a single Stage, some movements are protected (can proceed with the
20/// highest priority), while others are permitted (have to yield before proceeding).
21#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
22pub struct ControlTrafficSignal {
23    pub id: IntersectionID,
24    pub stages: Vec<Stage>,
25    pub offset: Duration,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
29pub struct Stage {
30    pub protected_movements: BTreeSet<MovementID>,
31    pub yield_movements: BTreeSet<MovementID>,
32    // TODO Not renaming this, because this is going to change radically in
33    // https://github.com/a-b-street/abstreet/pull/298 anyway
34    pub stage_type: StageType,
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
38pub enum StageType {
39    Fixed(Duration),
40    /// Minimum is the minimum duration, 0 allows cycle to be skipped if no demand.
41    /// Delay is the elapsed time with no demand that ends a cycle.
42    /// Additional is the additional duration for an extended cycle.
43    Variable(Duration, Duration, Duration),
44}
45
46impl StageType {
47    // TODO Maybe don't have this; force callers to acknowledge different policies
48    pub fn simple_duration(&self) -> Duration {
49        match self {
50            StageType::Fixed(d) => *d,
51            StageType::Variable(duration, _, _) => *duration,
52        }
53    }
54}
55
56impl ControlTrafficSignal {
57    pub fn new(map: &Map, id: IntersectionID) -> ControlTrafficSignal {
58        let mut policies = ControlTrafficSignal::get_possible_policies(map, id);
59        if policies.len() == 1 {
60            warn!("Falling back to greedy_assignment for {}", id);
61        }
62        policies.remove(0).1
63    }
64
65    pub fn get_possible_policies(
66        map: &Map,
67        id: IntersectionID,
68    ) -> Vec<(String, ControlTrafficSignal)> {
69        get_possible_policies(map, id)
70    }
71
72    pub fn get_min_crossing_time(&self, idx: usize, i: &Intersection) -> Duration {
73        let mut max_distance = Distance::meters(0.0);
74        for movement in &self.stages[idx].protected_movements {
75            if movement.crosswalk {
76                max_distance = max_distance.max(i.movements[movement].geom.length());
77            }
78        }
79        let time = max_distance / CROSSWALK_PACE;
80        assert!(time >= Duration::ZERO);
81        // Round up because it is converted to a usize elsewhere
82        Duration::seconds(time.inner_seconds().ceil())
83    }
84
85    pub fn validate(&self, i: &Intersection) -> Result<()> {
86        // Does the assignment cover the correct set of movements?
87        let expected_movements: BTreeSet<MovementID> = i.movements.keys().cloned().collect();
88        let mut actual_movements: BTreeSet<MovementID> = BTreeSet::new();
89        for stage in &self.stages {
90            actual_movements.extend(stage.protected_movements.iter());
91            actual_movements.extend(stage.yield_movements.iter());
92        }
93        if expected_movements != actual_movements {
94            bail!(
95                "Traffic signal assignment for {} broken. Missing {:?}, contains irrelevant {:?}",
96                self.id,
97                expected_movements
98                    .difference(&actual_movements)
99                    .cloned()
100                    .collect::<Vec<_>>(),
101                actual_movements
102                    .difference(&expected_movements)
103                    .cloned()
104                    .collect::<Vec<_>>()
105            );
106        }
107        for (stage_index, stage) in self.stages.iter().enumerate() {
108            // Do any of the priority movements in one stage conflict?
109            for m1 in stage.protected_movements.iter().map(|m| &i.movements[m]) {
110                for m2 in stage.protected_movements.iter().map(|m| &i.movements[m]) {
111                    if m1.conflicts_with(m2) {
112                        bail!(
113                            "Traffic signal has conflicting protected movements in one \
114                             stage:\n{:?}\n\n{:?}",
115                            m1,
116                            m2
117                        );
118                    }
119                }
120            }
121
122            // Do any of the crosswalks yield?
123            for m in stage.yield_movements.iter().map(|m| &i.movements[m]) {
124                // TODO Maybe make UnmarkedCrossing yield
125                assert!(!m.turn_type.pedestrian_crossing())
126            }
127            // Is there enough time in each stage to walk across the crosswalk
128            let min_crossing_time = self.get_min_crossing_time(stage_index, i);
129            if stage.stage_type.simple_duration() < min_crossing_time {
130                bail!(
131                    "Traffic signal does not allow enough time in stage to complete the \
132                     crosswalk\nStage Index{}\nStage : {:?}\nTime Required: {}\nTime Given: {}",
133                    stage_index,
134                    stage,
135                    min_crossing_time,
136                    stage.stage_type.simple_duration()
137                );
138            }
139        }
140        Ok(())
141    }
142
143    /// Move crosswalks from stages, adding them to an all-walk as last stage. This may promote
144    /// yields to protected. True is returned if any stages were added or modified.
145    pub fn convert_to_ped_scramble(&mut self, i: &Intersection) -> bool {
146        self.internal_convert_to_ped_scramble(true, i)
147    }
148    /// Move crosswalks from stages, adding them to an all-walk as last stage. This does not promote
149    /// yields to protected. True is returned if any stages were added or modified.
150    pub fn convert_to_ped_scramble_without_promotion(&mut self, i: &Intersection) -> bool {
151        self.internal_convert_to_ped_scramble(false, i)
152    }
153
154    fn internal_convert_to_ped_scramble(
155        &mut self,
156        promote_yield_to_protected: bool,
157        i: &Intersection,
158    ) -> bool {
159        let orig = self.clone();
160
161        let mut all_walk_stage = Stage::new();
162        for m in i.movements.values() {
163            if m.turn_type.pedestrian_crossing() {
164                all_walk_stage.edit_movement(m, TurnPriority::Protected);
165            }
166        }
167
168        // Remove Crosswalk and UnmarkedCrossing movements from existing stages.
169        let mut replaced = std::mem::take(&mut self.stages);
170        let mut has_all_walk = false;
171        for stage in replaced.iter_mut() {
172            if !has_all_walk && stage == &all_walk_stage {
173                has_all_walk = true;
174                continue;
175            }
176
177            // Crosswalks are only in protected_movements.
178            stage
179                .protected_movements
180                .retain(|m| !i.movements[m].turn_type.pedestrian_crossing());
181            if promote_yield_to_protected {
182                // Blindly try to promote yield movements to protected, now that crosswalks are
183                // gone.
184                let mut promoted = Vec::new();
185                for m in &stage.yield_movements {
186                    if stage.could_be_protected(*m, i) {
187                        stage.protected_movements.insert(*m);
188                        promoted.push(*m);
189                    }
190                }
191                for m in promoted {
192                    stage.yield_movements.remove(&m);
193                }
194            }
195        }
196        self.stages = replaced;
197
198        if !has_all_walk {
199            self.stages.push(all_walk_stage);
200        }
201        self != &orig
202    }
203
204    /// Modifies the fixed timing of all stages, applying either a major or minor duration,
205    /// depending on the relative rank of the roads involved in the intersection. If this
206    /// transformation couldn't be applied, returns an error. Even if an error is returned, the
207    /// signal may have been changed -- so only call this on a cloned signal.
208    pub fn adjust_major_minor_timing(
209        &mut self,
210        major: Duration,
211        minor: Duration,
212        map: &Map,
213    ) -> Result<()> {
214        if self.stages.len() != 2 {
215            bail!("This intersection doesn't have 2 stages.");
216        }
217
218        // What's the rank of each road?
219        let mut rank_per_road: BTreeMap<RoadID, usize> = BTreeMap::new();
220        for r in &map.get_i(self.id).roads {
221            rank_per_road.insert(*r, map.get_r(*r).get_detailed_rank());
222        }
223        let mut ranks: Vec<usize> = rank_per_road.values().cloned().collect();
224        ranks.sort_unstable();
225        ranks.dedup();
226        if ranks.len() == 1 {
227            bail!("This intersection doesn't have major/minor roads; they're all the same rank.");
228        }
229        let highest_rank = ranks.pop().unwrap();
230
231        // Try to apply the transformation
232        let orig = self.clone();
233        for stage in &mut self.stages {
234            match stage.stage_type {
235                StageType::Fixed(_) => {}
236                _ => bail!("This intersection doesn't use fixed timing."),
237            }
238            // Ignoring crosswalks, do any of the turns come from a major road?
239            if stage
240                .protected_movements
241                .iter()
242                .any(|m| !m.crosswalk && highest_rank == rank_per_road[&m.from.road])
243            {
244                stage.stage_type = StageType::Fixed(major);
245            } else {
246                stage.stage_type = StageType::Fixed(minor);
247            }
248        }
249
250        if self.simple_cycle_duration() != major + minor {
251            bail!("This intersection didn't already group major/minor roads together.");
252        }
253
254        if self == &orig {
255            bail!("This change had no effect.");
256        }
257
258        Ok(())
259    }
260
261    pub fn missing_turns(&self, i: &Intersection) -> BTreeSet<MovementID> {
262        let mut missing: BTreeSet<MovementID> = i.movements.keys().cloned().collect();
263        for stage in &self.stages {
264            for m in &stage.protected_movements {
265                missing.remove(m);
266            }
267            for m in &stage.yield_movements {
268                missing.remove(m);
269            }
270        }
271        missing
272    }
273
274    /// How long a full cycle of the signal lasts, assuming no actuated timings.
275    pub fn simple_cycle_duration(&self) -> Duration {
276        let mut total = Duration::ZERO;
277        for s in &self.stages {
278            total += s.stage_type.simple_duration();
279        }
280        total
281    }
282}
283
284impl Stage {
285    pub fn new() -> Stage {
286        Stage {
287            protected_movements: BTreeSet::new(),
288            yield_movements: BTreeSet::new(),
289            // TODO Set a default
290            stage_type: StageType::Fixed(Duration::seconds(30.0)),
291        }
292    }
293
294    pub fn could_be_protected(&self, m1: MovementID, i: &Intersection) -> bool {
295        let movement1 = &i.movements[&m1];
296        for m2 in &self.protected_movements {
297            if m1 == *m2 || movement1.conflicts_with(&i.movements[m2]) {
298                return false;
299            }
300        }
301        true
302    }
303
304    pub fn get_priority_of_turn(&self, t: TurnID, i: &Intersection) -> TurnPriority {
305        self.get_priority_of_movement(i.turn_to_movement(t).0)
306    }
307
308    pub fn get_priority_of_movement(&self, m: MovementID) -> TurnPriority {
309        if self.protected_movements.contains(&m) {
310            TurnPriority::Protected
311        } else if self.yield_movements.contains(&m) {
312            TurnPriority::Yield
313        } else {
314            TurnPriority::Banned
315        }
316    }
317
318    pub fn edit_movement(&mut self, g: &Movement, pri: TurnPriority) {
319        if g.turn_type.pedestrian_crossing() {
320            self.enforce_minimum_crosswalk_time(g);
321        }
322        self.protected_movements.remove(&g.id);
323        self.yield_movements.remove(&g.id);
324        if pri == TurnPriority::Protected {
325            self.protected_movements.insert(g.id);
326        } else if pri == TurnPriority::Yield {
327            self.yield_movements.insert(g.id);
328        }
329    }
330    pub fn enforce_minimum_crosswalk_time(&mut self, movement: &Movement) {
331        // Round up to an int, because it is exported as a usize
332        let time = Duration::seconds(
333            (movement.geom.length() / CROSSWALK_PACE)
334                .inner_seconds()
335                .ceil(),
336        );
337        if time > self.stage_type.simple_duration() {
338            self.stage_type = match self.stage_type {
339                StageType::Fixed(_) => StageType::Fixed(time),
340                StageType::Variable(_, delay, additional) => {
341                    StageType::Variable(time, delay, additional)
342                }
343            };
344        }
345    }
346
347    // A trivial function that returns max crosswalk time if the stage is just crosswalks.
348    pub fn max_crosswalk_time(&self, i: &Intersection) -> Option<Duration> {
349        let mut max_distance = Distance::const_meters(0.0);
350        for m in &self.protected_movements {
351            if m.crosswalk {
352                max_distance = max_distance.max(i.movements[m].geom.length());
353            } else {
354                return None;
355            }
356        }
357        if max_distance > Distance::const_meters(0.0) {
358            let time = max_distance / CROSSWALK_PACE;
359            assert!(time >= Duration::ZERO);
360            // Round up because it is converted to a usize elsewhere
361            Some(Duration::seconds(time.inner_seconds().ceil()))
362        } else {
363            None
364        }
365    }
366}
367
368impl ControlTrafficSignal {
369    pub fn export(&self, map: &Map) -> perma_traffic_signal::TrafficSignal {
370        perma_traffic_signal::TrafficSignal {
371            intersection_osm_node_id: map.get_i(self.id).orig_id.0,
372            plans: vec![perma_traffic_signal::Plan {
373                start_time_seconds: 0,
374                stages: self
375                    .stages
376                    .iter()
377                    .map(|s| perma_traffic_signal::Stage {
378                        protected_turns: s
379                            .protected_movements
380                            .iter()
381                            .map(|mvmnt| mvmnt.to_permanent(map))
382                            .collect(),
383                        permitted_turns: s
384                            .yield_movements
385                            .iter()
386                            .map(|mvmnt| mvmnt.to_permanent(map))
387                            .collect(),
388                        stage_type: match s.stage_type {
389                            StageType::Fixed(d) => {
390                                perma_traffic_signal::StageType::Fixed(d.inner_seconds() as usize)
391                            }
392                            StageType::Variable(min, delay, additional) => {
393                                perma_traffic_signal::StageType::Variable(
394                                    min.inner_seconds() as usize,
395                                    delay.inner_seconds() as usize,
396                                    additional.inner_seconds() as usize,
397                                )
398                            }
399                        },
400                    })
401                    .collect(),
402                offset_seconds: self.offset.inner_seconds() as usize,
403            }],
404        }
405    }
406
407    pub(crate) fn import(
408        mut raw: perma_traffic_signal::TrafficSignal,
409        id: IntersectionID,
410        map: &Map,
411    ) -> Result<ControlTrafficSignal> {
412        // TODO Only import the first plan. Will import all of them later.
413        let plan = raw.plans.remove(0);
414        let mut stages = Vec::new();
415        for s in plan.stages {
416            let mut errors = Vec::new();
417            let mut protected_movements = BTreeSet::new();
418            for t in s.protected_turns {
419                match MovementID::from_permanent(t, map) {
420                    Ok(mvmnt) => {
421                        protected_movements.insert(mvmnt);
422                    }
423                    Err(err) => {
424                        errors.push(err.to_string());
425                    }
426                }
427            }
428            let mut permitted_movements = BTreeSet::new();
429            for t in s.permitted_turns {
430                match MovementID::from_permanent(t, map) {
431                    Ok(mvmnt) => {
432                        permitted_movements.insert(mvmnt);
433                    }
434                    Err(err) => {
435                        errors.push(err.to_string());
436                    }
437                }
438            }
439            if errors.is_empty() {
440                stages.push(Stage {
441                    protected_movements,
442                    yield_movements: permitted_movements,
443                    stage_type: match s.stage_type {
444                        perma_traffic_signal::StageType::Fixed(d) => {
445                            StageType::Fixed(Duration::seconds(d as f64))
446                        }
447                        perma_traffic_signal::StageType::Variable(min, delay, additional) => {
448                            StageType::Variable(
449                                Duration::seconds(min as f64),
450                                Duration::seconds(delay as f64),
451                                Duration::seconds(additional as f64),
452                            )
453                        }
454                    },
455                });
456            } else {
457                bail!("{}", errors.join("; "));
458            }
459        }
460        let ts = ControlTrafficSignal {
461            id,
462            stages,
463            offset: Duration::seconds(plan.offset_seconds as f64),
464        };
465        ts.validate(map.get_i(id))?;
466        Ok(ts)
467    }
468}