map_model/make/
walking_turns.rs

1use geom::{Distance, PolyLine, Pt2D, EPSILON_DIST};
2
3use crate::{
4    Direction, DrivingSide, Intersection, IntersectionID, Lane, LaneID, Map, Turn, TurnID, TurnType,
5};
6
7/// Looks at all sidewalks (or lack thereof) in counter-clockwise order around an intersection.
8/// Based on adjacency, create a SharedSidewalkCorner or a Crosswalk.
9/// UnmarkedCrossings are not generated here; another process later "downgrades" crosswalks to
10/// unmarked.
11pub fn make_walking_turns(map: &Map, i: &Intersection) -> Vec<Turn> {
12    let driving_side = map.config.driving_side;
13
14    // Consider all roads in counter-clockwise order. Every road has up to two sidewalks. Gather
15    // those in order, remembering what roads don't have them.
16    let mut lanes: Vec<Option<&Lane>> = Vec::new();
17    let mut sorted_roads = i.roads.clone();
18    // And for left-handed driving, we need to walk around in the opposite order.
19    if driving_side == DrivingSide::Left {
20        sorted_roads.reverse();
21    }
22
23    for r in sorted_roads {
24        let road = map.get_r(r);
25        let mut fwd = None;
26        let mut back = None;
27        for l in &road.lanes {
28            if l.lane_type.is_walkable() {
29                if l.dir == Direction::Fwd {
30                    fwd = Some(l);
31                } else {
32                    back = Some(l);
33                }
34            }
35        }
36
37        let (in_lane, out_lane) = if road.src_i == i.id {
38            (back, fwd)
39        } else {
40            (fwd, back)
41        };
42
43        // Don't add None entries for footways even if they only have one lane
44        if map.get_r(r).is_footway() {
45            if in_lane.is_some() {
46                lanes.push(in_lane);
47            }
48            if out_lane.is_some() {
49                lanes.push(out_lane);
50            }
51        } else {
52            lanes.push(in_lane);
53            lanes.push(out_lane);
54        }
55    }
56
57    // If there are 0 or 1 sidewalks there are no turns to be made
58    if lanes.iter().filter(|l| l.is_some()).count() <= 1 {
59        return Vec::new();
60    }
61
62    // At a deadend make only one SharedSidewalkCorner
63    if i.is_deadend_for_everyone() {
64        let (l1, l2) = (lanes[0].unwrap(), lanes[1].unwrap());
65        return vec![Turn {
66            id: turn_id(i.id, l1.id, l2.id),
67            turn_type: TurnType::SharedSidewalkCorner,
68            geom: make_shared_sidewalk_corner(i, l1, l2),
69        }];
70    }
71
72    // Make sure we start with a sidewalk.
73    while lanes[0].is_none() {
74        lanes.rotate_left(1);
75    }
76    let mut result: Vec<Turn> = Vec::new();
77
78    let mut from: Option<&Lane> = lanes[0];
79    let mut adj = true;
80    for l in lanes.iter().skip(1).chain(lanes.iter().take(1)) {
81        if from.is_none() {
82            from = *l;
83            adj = true;
84            continue;
85        }
86        let l1 = from.unwrap();
87
88        if l.is_none() {
89            adj = false;
90            continue;
91        }
92        let l2 = l.unwrap();
93
94        if adj && l1.id.road != l2.id.road {
95            result.push(Turn {
96                id: turn_id(i.id, l1.id, l2.id),
97                turn_type: TurnType::SharedSidewalkCorner,
98                geom: make_shared_sidewalk_corner(i, l1, l2),
99            });
100
101            from = Some(l2);
102        // adj stays true
103        } else {
104            result.push(Turn {
105                id: turn_id(i.id, l1.id, l2.id),
106                turn_type: TurnType::Crosswalk,
107                geom: make_crosswalk(i, l1, l2),
108            });
109            from = Some(l2);
110            adj = true;
111        }
112    }
113
114    // If there are exactly two crosswalks they must be connected or opposite, so delete one.
115    // This happens at degenerate intersections with sidewalks on both sides or where a
116    //  footway crosses a road without sidewalks.
117    // If there is one crosswalk it must be opposite to a SharedSidewalkCorner, because the
118    //  above could never create just one turn and starts and ends in the same place.
119    // This happens at degenerate intersections with sidewalks on one side.
120    match result
121        .iter()
122        .filter(|t| t.turn_type == TurnType::Crosswalk)
123        .count()
124    {
125        1 | 2 => {
126            result.remove(
127                result
128                    .iter()
129                    .position(|t| t.turn_type == TurnType::Crosswalk)
130                    .unwrap(),
131            );
132        }
133        _ => {}
134    }
135
136    result
137}
138
139/// Filter out crosswalks on really short roads. In reality, these roads are usually located within
140/// an intersection, which isn't a valid place for a pedestrian crossing.
141///
142/// And if the road is marked as having no crosswalks at an end, downgrade them to unmarked
143/// crossings.
144pub fn filter_turns(mut input: Vec<Turn>, map: &Map, i: &Intersection) -> Vec<Turn> {
145    for r in &i.roads {
146        if map.get_r(*r).is_extremely_short() {
147            input.retain(|t| {
148                !(t.id.src.road == *r && t.id.dst.road == *r && t.turn_type.pedestrian_crossing())
149            });
150        }
151    }
152
153    for turn in &mut input {
154        if let Some(dr) = turn.crosswalk_over_road(map) {
155            let road = map.get_r(dr.road);
156            let keep = if dr.dir == Direction::Fwd {
157                road.crosswalk_forward
158            } else {
159                road.crosswalk_backward
160            };
161            if !keep {
162                turn.turn_type = TurnType::UnmarkedCrossing;
163            }
164        } else if turn.turn_type.pedestrian_crossing() {
165            // We have a crosswalk over multiple roads (or sometimes, just one road that only has a
166            // walkable lane on one side of it). We can't yet detect all the roads crossed. So for
167            // now, it's more often correct to assume that if any nearby roads don't have a
168            // crossing snapped to both ends, then there's probably no crosswalk here.
169            for l in [turn.id.src, turn.id.dst] {
170                let road = map.get_parent(l);
171                if !road.crosswalk_forward || !road.crosswalk_backward {
172                    turn.turn_type = TurnType::UnmarkedCrossing;
173                }
174            }
175        }
176    }
177
178    input
179}
180
181fn make_crosswalk(i: &Intersection, l1: &Lane, l2: &Lane) -> PolyLine {
182    let l1_line = l1.end_line(i.id);
183    let l2_line = l2.end_line(i.id);
184
185    // Jut out a bit into the intersection, cross over, then jut back in.
186    // Put degenerate intersection crosswalks in the middle (DEGENERATE_HALF_LENGTH).
187    PolyLine::deduping_new(vec![
188        l1_line.pt2(),
189        l1_line.unbounded_dist_along(
190            l1_line.length()
191                + if i.is_degenerate() {
192                    Distance::const_meters(2.5)
193                } else {
194                    l1.width / 2.0
195                },
196        ),
197        l2_line.unbounded_dist_along(
198            l2_line.length()
199                + if i.is_degenerate() {
200                    Distance::const_meters(2.5)
201                } else {
202                    l2.width / 2.0
203                },
204        ),
205        l2_line.pt2(),
206    ])
207    .unwrap_or_else(|_| baseline_geometry(l1.endpoint(i.id), l2.endpoint(i.id)))
208}
209
210// TODO This doesn't handle sidewalk/shoulder transitions
211fn make_shared_sidewalk_corner(i: &Intersection, l1: &Lane, l2: &Lane) -> PolyLine {
212    // We'll fallback to this if the fancier geometry fails.
213    let baseline = baseline_geometry(l1.endpoint(i.id), l2.endpoint(i.id));
214
215    // Is point2 counter-clockwise of point1?
216    let dir = if i
217        .polygon
218        .center()
219        .angle_to(l1.endpoint(i.id))
220        .simple_shortest_rotation_towards(i.polygon.center().angle_to(l2.endpoint(i.id)))
221        > 0.0
222    {
223        1.0
224    } else {
225        -1.0
226        // For deadends, go the long way around
227    } * if i.is_deadend_for_everyone() {
228        -1.0
229    } else {
230        1.0
231    };
232    // Find all of the points on the intersection polygon between the two sidewalks. Assumes
233    // sidewalks are the same length.
234    let corner1 = l1
235        .end_line(i.id)
236        .shift_either_direction(dir * l1.width / 2.0)
237        .pt2();
238    let corner2 = l2
239        .end_line(i.id)
240        .shift_either_direction(-dir * l2.width / 2.0)
241        .pt2();
242
243    // TODO Something like this will be MUCH simpler and avoid going around the long way sometimes.
244    if false {
245        return i
246            .polygon
247            .get_outer_ring()
248            .get_shorter_slice_btwn(corner1, corner2)
249            .unwrap();
250    }
251
252    // The order of the points here seems backwards, but it's because we scan from corner2
253    // to corner1 below.
254    let mut pts_between = vec![l2.endpoint(i.id)];
255    // Intersection polygons are constructed in clockwise order, so do corner2 to corner1.
256    let mut i_pts = i.polygon.get_outer_ring().clone().into_points();
257
258    // last pt = first_pt
259    i_pts.pop();
260
261    if dir < 0.0 {
262        i_pts.reverse();
263    }
264
265    for _ in 0..i_pts.len() {
266        if i_pts[0].approx_eq(corner2, Distance::meters(0.5)) {
267            break;
268        }
269        i_pts.rotate_left(1);
270    }
271
272    for idx in 0..i_pts.len() {
273        if i_pts[idx].approx_eq(corner1, Distance::meters(0.5)) {
274            i_pts.truncate(idx + 1);
275            break;
276        }
277    }
278
279    if i_pts.len() < 2 {
280        // no intermediate points, so just do a straight line
281        return baseline;
282    }
283
284    if let Ok(pl) = PolyLine::new(i_pts)
285        .and_then(|pl| pl.shift_either_direction(dir * l1.width.min(l2.width) / 2.0))
286    {
287        // The first and last points should be approximately l2's and l1's endpoints
288        pts_between.extend(pl.points().iter().take(pl.points().len() - 1).skip(1));
289    } else {
290        warn!(
291            "SharedSidewalkCorner between {} and {} has weird collapsing geometry, so \
292                just doing straight line",
293            l1.id, l2.id
294        );
295        return baseline;
296    }
297
298    pts_between.push(l1.endpoint(i.id));
299    pts_between.dedup();
300
301    pts_between.reverse();
302
303    if abstutil::contains_duplicates(
304        &pts_between
305            .iter()
306            .map(|pt| pt.to_hashable())
307            .collect::<Vec<_>>(),
308    ) || pts_between.len() < 2
309    {
310        warn!(
311            "SharedSidewalkCorner between {} and {} has weird duplicate geometry, so just doing \
312             straight line",
313            l1.id, l2.id
314        );
315        return baseline;
316    }
317    if let Ok(result) = PolyLine::new(pts_between) {
318        if result.length() > 10.0 * baseline.length() {
319            warn!(
320                "SharedSidewalkCorner between {} and {} explodes to {} long, so just doing straight \
321                 line",
322                l1.id,
323                l2.id,
324                result.length()
325            );
326            return baseline;
327        }
328        result
329    } else {
330        baseline
331    }
332}
333
334// Never in any circumstance should we produce a polyline with only one point (or two points
335// that're equal), because it'll just crash downstream rendering logic and make a mess elsewhere.
336// Avoid that here. The result is unlikely to look correct (or be easily visible at all).
337//
338// TODO Proper fix is likely to make a turn's geometry optional.
339fn baseline_geometry(pt1: Pt2D, pt2: Pt2D) -> PolyLine {
340    PolyLine::new(vec![pt1, pt2]).unwrap_or_else(|_| {
341        PolyLine::must_new(vec![
342            pt1,
343            pt1.offset(EPSILON_DIST.inner_meters(), EPSILON_DIST.inner_meters()),
344        ])
345    })
346}
347
348fn turn_id(parent: IntersectionID, src: LaneID, dst: LaneID) -> TurnID {
349    TurnID { parent, src, dst }
350}