map_model/objects/
modal_filter.rs

1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use geom::{Distance, Line};
6
7use crate::{EditCmd, IntersectionID, Map, PathConstraints, RoadID};
8
9/// The type of a modal filter. Most of these don't have semantics yet; the variation is just for
10/// visual representation
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
12pub enum FilterType {
13    NoEntry,
14    WalkCycleOnly,
15    BusGate,
16    SchoolStreet,
17}
18
19/// A filter placed somewhere along a road
20#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
21pub struct RoadFilter {
22    pub dist: Distance,
23    pub filter_type: FilterType,
24}
25
26impl RoadFilter {
27    pub fn new(dist: Distance, filter_type: FilterType) -> Self {
28        Self { dist, filter_type }
29    }
30}
31
32/// A diagonal filter exists in an intersection. It's defined by two roads (the order is
33/// arbitrary). When all of the intersection's roads are sorted in clockwise order, this pair of
34/// roads splits the ordering into two groups. Turns in each group are still possible, but not
35/// across groups.
36///
37/// Be careful with `PartialEq` -- see `approx_eq`.
38#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
39pub struct DiagonalFilter {
40    pub i: IntersectionID,
41    pub r1: RoadID,
42    pub r2: RoadID,
43    pub filter_type: FilterType,
44
45    pub group1: BTreeSet<RoadID>,
46    pub group2: BTreeSet<RoadID>,
47}
48
49impl DiagonalFilter {
50    /// The caller must call this in a `before_edit` / `redraw_all_icons` "transaction."
51    pub fn cycle_through_alternatives(
52        map: &Map,
53        i: IntersectionID,
54        filter_type: FilterType,
55    ) -> Vec<EditCmd> {
56        let mut roads = map.get_i(i).roads.clone();
57        // Don't consider non-driveable roads for the 4-way calculation even
58        // TODO This should check access=no,private too
59        roads.retain(|r| PathConstraints::Car.can_use_road(map.get_r(*r), map));
60
61        let mut commands = Vec::new();
62
63        if roads.len() == 4 {
64            // 4-way intersections are the only place where true diagonal filters can be placed
65            let alt1 = DiagonalFilter::new(map, i, roads[0], roads[1], filter_type);
66            let alt2 = DiagonalFilter::new(map, i, roads[1], roads[2], filter_type);
67
68            match map.get_i(i).modal_filter {
69                Some(ref prev) => {
70                    if alt1.approx_eq(prev) {
71                        commands.push(map.edit_intersection_cmd(i, |new| {
72                            new.modal_filter = Some(alt2);
73                        }));
74                    } else if alt2.approx_eq(prev) {
75                        commands.push(map.edit_intersection_cmd(i, |new| {
76                            new.modal_filter = None;
77                        }));
78                    } else {
79                        unreachable!()
80                    }
81                }
82                None => {
83                    commands.push(map.edit_intersection_cmd(i, |new| {
84                        new.modal_filter = Some(alt1);
85                    }));
86                }
87            }
88        } else if roads.len() > 1 {
89            // Diagonal filters elsewhere don't really make sense. They're equivalent to filtering
90            // one road. Just cycle through those.
91
92            // But skip roads that're aren't filterable
93            roads.retain(|r| {
94                let road = map.get_r(*r);
95                road.oneway_for_driving().is_none() && !road.is_deadend_for_driving(map)
96            });
97
98            // TODO I triggered this case somewhere in Kennington when drawing free-hand. Look for
99            // the case and test this case more carefully. Maybe do the filtering earlier.
100            if roads.is_empty() {
101                return commands;
102            }
103
104            let mut add_filter_to = None;
105            if let Some(idx) = roads
106                .iter()
107                .position(|r| map.get_r(*r).modal_filter.is_some())
108            {
109                commands.push(map.edit_road_cmd(roads[idx], |new| {
110                    new.modal_filter = None;
111                }));
112                if idx != roads.len() - 1 {
113                    add_filter_to = Some(roads[idx + 1]);
114                }
115            } else {
116                add_filter_to = Some(roads[0]);
117            }
118            if let Some(r) = add_filter_to {
119                let road = map.get_r(r);
120                let dist = if i == road.src_i {
121                    Distance::ZERO
122                } else {
123                    road.length()
124                };
125                commands.push(map.edit_road_cmd(r, |new| {
126                    new.modal_filter = Some(RoadFilter::new(dist, filter_type));
127                }));
128            }
129        }
130        commands
131    }
132
133    fn new(
134        map: &Map,
135        i: IntersectionID,
136        r1: RoadID,
137        r2: RoadID,
138        filter_type: FilterType,
139    ) -> DiagonalFilter {
140        let mut roads = map.get_i(i).roads.clone();
141        // Make self.r1 be the first entry
142        while roads[0] != r1 {
143            roads.rotate_right(1);
144        }
145
146        let mut group1 = BTreeSet::new();
147        group1.insert(roads.remove(0));
148        loop {
149            let next = roads.remove(0);
150            group1.insert(next);
151            if next == r2 {
152                break;
153            }
154        }
155        // This is only true for 4-ways...
156        assert_eq!(group1.len(), 2);
157        assert_eq!(roads.len(), 2);
158
159        DiagonalFilter {
160            r1,
161            r2,
162            i,
163            filter_type,
164            group1,
165            group2: roads.into_iter().collect(),
166        }
167    }
168
169    /// Physically where is the filter placed?
170    pub fn geometry(&self, map: &Map) -> Line {
171        let r1 = map.get_r(self.r1);
172        let r2 = map.get_r(self.r2);
173
174        // Orient the road to face the intersection
175        let pl1 = r1.center_pts.maybe_reverse(r1.src_i == self.i);
176        let pl2 = r2.center_pts.maybe_reverse(r2.src_i == self.i);
177
178        // The other combinations of left/right here would produce points or a line across just one
179        // road
180        let pt1 = pl1.must_shift_right(r1.get_half_width()).last_pt();
181        let pt2 = pl2.must_shift_left(r2.get_half_width()).last_pt();
182        match Line::new(pt1, pt2) {
183            Ok(line) => line,
184            // Very rarely, this line is too small. If that happens, just draw something roughly in
185            // the right place
186            Err(_) => Line::must_new(
187                pt1,
188                pt1.project_away(r1.get_half_width(), pt1.angle_to(pt2)),
189            ),
190        }
191    }
192
193    pub fn allows_turn(&self, from: RoadID, to: RoadID) -> bool {
194        self.group1.contains(&from) == self.group1.contains(&to)
195    }
196
197    pub fn avoid_movements_between_roads(&self) -> Vec<(RoadID, RoadID)> {
198        let mut pairs = Vec::new();
199        for from in &self.group1 {
200            for to in &self.group2 {
201                pairs.push((*from, *to));
202                pairs.push((*to, *from));
203            }
204        }
205        pairs
206    }
207
208    fn approx_eq(&self, other: &DiagonalFilter) -> bool {
209        // Careful. At a 4-way intersection, the same filter can be expressed as a different pair of two
210        // roads. The (r1, r2) ordering is also arbitrary. cycle_through_alternatives is
211        // consistent, though.
212        //
213        // Note this ignores filter_type.
214        (self.r1, self.r2, self.i, &self.group1, &self.group2)
215            == (other.r1, other.r2, other.i, &other.group1, &other.group2)
216    }
217}