map_model/objects/
intersection.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3
4use serde::{Deserialize, Serialize};
5
6use abstutil::{deserialize_usize, serialize_usize};
7use geom::{Distance, Polygon};
8
9use crate::{
10    osm, CompressedMovementID, DiagonalFilter, DirectedRoadID, IntersectionControl,
11    IntersectionKind, LaneID, Map, Movement, MovementID, PathConstraints, Road, RoadID, RoadSideID,
12    SideOfRoad, Turn, TurnID,
13};
14
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
16pub struct IntersectionID(
17    #[serde(
18        serialize_with = "serialize_usize",
19        deserialize_with = "deserialize_usize"
20    )]
21    pub usize,
22);
23
24impl fmt::Display for IntersectionID {
25    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26        write!(f, "Intersection #{}", self.0)
27    }
28}
29
30/// An intersection connects roads. Most have >2 roads and are controlled by stop signs or traffic
31/// signals. Roads that lead to the boundary of the map end at border intersections, with only that
32/// one road attached.
33#[derive(Serialize, Deserialize, Clone, Debug)]
34pub struct Intersection {
35    pub id: IntersectionID,
36    /// This needs to be in clockwise orientation, or later rendering of sidewalk corners breaks.
37    pub polygon: Polygon,
38    pub turns: Vec<Turn>,
39    pub elevation: Distance,
40
41    pub kind: IntersectionKind,
42    pub control: IntersectionControl,
43    pub orig_id: osm::NodeID,
44
45    /// Note that a lane may belong to both incoming_lanes and outgoing_lanes.
46    // TODO narrow down when and why. is it just sidewalks in weird cases?
47    // TODO Change to BTreeSet, or otherwise emphasize to callers that the order of these isn't
48    // meaningful
49    pub incoming_lanes: Vec<LaneID>,
50    pub outgoing_lanes: Vec<LaneID>,
51
52    // These're ordered clockwise around the intersection
53    pub roads: Vec<RoadID>,
54
55    pub modal_filter: Option<DiagonalFilter>,
56
57    /// Was a short road adjacent to this intersection merged?
58    pub merged: bool,
59    // These increase the map file size, so instead, just use `recalculate_all_movements` after
60    // deserializing.
61    #[serde(skip_serializing, skip_deserializing)]
62    pub movements: BTreeMap<MovementID, Movement>,
63}
64
65impl Intersection {
66    pub fn is_border(&self) -> bool {
67        self.kind == IntersectionKind::MapEdge
68    }
69    pub fn is_incoming_border(&self) -> bool {
70        self.kind == IntersectionKind::MapEdge && !self.outgoing_lanes.is_empty()
71    }
72    pub fn is_outgoing_border(&self) -> bool {
73        self.kind == IntersectionKind::MapEdge && !self.incoming_lanes.is_empty()
74    }
75
76    pub fn is_closed(&self) -> bool {
77        self.control == IntersectionControl::Construction
78    }
79
80    pub fn is_stop_sign(&self) -> bool {
81        self.control == IntersectionControl::Signed
82    }
83
84    pub fn is_traffic_signal(&self) -> bool {
85        self.control == IntersectionControl::Signalled
86    }
87
88    pub fn is_light_rail(&self, map: &Map) -> bool {
89        self.roads.iter().all(|r| map.get_r(*r).is_light_rail())
90    }
91
92    pub fn is_private(&self, map: &Map) -> bool {
93        self.roads.iter().all(|r| map.get_r(*r).is_private())
94    }
95
96    pub fn is_footway(&self, map: &Map) -> bool {
97        self.roads.iter().all(|r| map.get_r(*r).is_footway())
98    }
99
100    pub fn is_cycleway(&self, map: &Map) -> bool {
101        self.roads.iter().all(|r| map.get_r(*r).is_cycleway())
102    }
103
104    /// Does this intersection only connect two road segments? Then usually, the intersection only
105    /// exists to mark the road name or lanes changing.
106    pub fn is_degenerate(&self) -> bool {
107        self.roads.len() == 2
108    }
109
110    /// Does this intersection connect to only a single driveable road segment?
111    pub fn is_deadend_for_driving(&self, map: &Map) -> bool {
112        // If a driveable road leads only to a cycle lane, then that's still a dead-end for driving
113        self.roads
114            .iter()
115            .filter(|r| PathConstraints::Car.can_use_road(map.get_r(**r), map))
116            .count()
117            == 1
118    }
119
120    /// Ignoring mode of travel, is this intersection only connected to one road?
121    pub fn is_deadend_for_everyone(&self) -> bool {
122        self.roads.len() == 1
123    }
124
125    pub fn get_incoming_lanes(&self, map: &Map, constraints: PathConstraints) -> Vec<LaneID> {
126        self.incoming_lanes
127            .iter()
128            .filter(move |l| constraints.can_use(map.get_l(**l), map))
129            .cloned()
130            .collect()
131    }
132
133    /// Strict for bikes. If there are bike lanes, not allowed to use other lanes.
134    pub fn get_outgoing_lanes(&self, map: &Map, constraints: PathConstraints) -> Vec<LaneID> {
135        constraints.filter_lanes(self.outgoing_lanes.clone(), map)
136    }
137
138    /// Higher numbers get drawn on top
139    pub fn get_zorder(&self, map: &Map) -> isize {
140        // TODO Not sure min makes sense -- what about a 1 and a 0? Prefer the nonzeros. If there's
141        // a -1 and a 1... need to see it to know what to do.
142        self.roads
143            .iter()
144            .map(|r| map.get_r(*r).zorder)
145            .min()
146            .unwrap()
147    }
148
149    pub fn get_rank(&self, map: &Map) -> osm::RoadRank {
150        self.roads
151            .iter()
152            .map(|r| map.get_r(*r).get_rank())
153            .max()
154            .unwrap()
155    }
156
157    // TODO Use osm2streets RoadEdge?
158    // This skips the "interior" piece of any loop roads
159    pub fn get_road_sides_sorted(&self, map: &Map) -> Vec<RoadSideID> {
160        // TODO For loop roads, osm2streets repeats the roads! Consider upstream fixes.
161        let mut deduped = self.roads.clone();
162        deduped.dedup();
163
164        let mut sides = Vec::new();
165        for r in deduped {
166            let r = map.get_r(r);
167            if r.src_i == r.dst_i {
168                // For loop roads, one of the sides is "interior" and should just be skipped. The
169                // side depends on clockwise orientation -- just sketching it out on paper is
170                // enough to be convincing.
171                if r.center_pts.is_clockwise() {
172                    sides.push(RoadSideID {
173                        road: r.id,
174                        side: SideOfRoad::Left,
175                    });
176                } else {
177                    sides.push(RoadSideID {
178                        road: r.id,
179                        side: SideOfRoad::Right,
180                    });
181                }
182                continue;
183            }
184            if r.dst_i == self.id {
185                sides.push(RoadSideID {
186                    road: r.id,
187                    side: SideOfRoad::Right,
188                });
189                sides.push(RoadSideID {
190                    road: r.id,
191                    side: SideOfRoad::Left,
192                });
193            } else {
194                sides.push(RoadSideID {
195                    road: r.id,
196                    side: SideOfRoad::Left,
197                });
198                sides.push(RoadSideID {
199                    road: r.id,
200                    side: SideOfRoad::Right,
201                });
202            }
203        }
204        sides
205    }
206
207    /// Return all incoming roads to an intersection, sorted by angle. This skips one-way roads
208    /// outbound from the intersection, since no turns originate from those anyway. This allows
209    /// heuristics for a 3-way intersection to not care if one of the roads happens to be a dual
210    /// carriageway (split into two one-ways).
211    pub fn get_sorted_incoming_roads(&self, map: &Map) -> Vec<RoadID> {
212        let mut roads = self.roads.clone();
213        roads.retain(|r| !map.get_r(*r).incoming_lanes(self.id).is_empty());
214        roads
215    }
216
217    pub fn some_outgoing_road(&self, map: &Map) -> Option<DirectedRoadID> {
218        self.outgoing_lanes
219            .get(0)
220            .map(|l| map.get_l(*l).get_directed_parent())
221    }
222
223    pub fn some_incoming_road(&self, map: &Map) -> Option<DirectedRoadID> {
224        self.incoming_lanes
225            .get(0)
226            .map(|l| map.get_l(*l).get_directed_parent())
227    }
228
229    pub fn name(&self, lang: Option<&String>, map: &Map) -> String {
230        let road_names = self
231            .roads
232            .iter()
233            .map(|r| map.get_r(*r).get_name(lang))
234            .collect::<BTreeSet<_>>();
235        abstutil::plain_list_names(road_names)
236    }
237
238    /// Don't call for SharedSidewalkCorners
239    pub fn turn_to_movement(&self, turn: TurnID) -> (MovementID, CompressedMovementID) {
240        for (idx, m) in self.movements.values().enumerate() {
241            if m.members.contains(&turn) {
242                return (
243                    m.id,
244                    CompressedMovementID {
245                        i: self.id,
246                        idx: u8::try_from(idx).unwrap(),
247                    },
248                );
249            }
250        }
251
252        panic!(
253            "{} doesn't belong to any movements in {} or is a SharedSidewalkCorner maybe",
254            turn, self.id
255        )
256    }
257
258    pub fn find_road_between<'a>(&self, other: IntersectionID, map: &'a Map) -> Option<&'a Road> {
259        for r in &self.roads {
260            let road = map.get_r(*r);
261            if road.other_endpt(self.id) == other {
262                return Some(road);
263            }
264        }
265        None
266    }
267}