Only make crosswalks in one direction.

Don't create duplicate crosswalks in edit_movement

Remove other_crosswalk_ids and switch to make_walking_turns_v2

Allow make_shared_sidewalk_corner and make_crosswalks to work in either direction with a point order check

Don't skip rendering some corners since they're no longer duplicated

Draw sidewalk corners the same regardless of lane direction with a point order check

Only make one crosswalk at dead ends and degenerate intersections

Make footways only get sharedsidewalkcorner turns

Don't panic on bad sharedsidewalkcorner geometry
This commit is contained in:
Marcel Dejean 2021-12-02 20:52:33 -05:00 committed by Dustin Carlino
parent 65f086ecb3
commit 9369ac229f
5 changed files with 238 additions and 505 deletions

View File

@ -55,10 +55,7 @@ impl DrawIntersection {
}
for turn in &i.turns {
// Avoid double-rendering
if turn.turn_type.pedestrian_crossing()
&& !turn.other_crosswalk_ids.iter().any(|id| *id < turn.id)
{
if turn.turn_type.pedestrian_crossing() {
make_crosswalk(&mut default_geom, turn, map, app.cs());
}
}
@ -265,10 +262,6 @@ pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
for turn in &i.turns {
if turn.turn_type == TurnType::SharedSidewalkCorner {
// Avoid double-rendering
if map.get_l(turn.id.src).dst_i != i.id {
continue;
}
let l1 = map.get_l(turn.id.src);
let l2 = map.get_l(turn.id.dst);
@ -278,23 +271,36 @@ pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
continue;
}
// Is point2 counter-clockwise of point1?
let dir = if i
.polygon
.center()
.angle_to(turn.geom.first_pt())
.simple_shortest_rotation_towards(i.polygon.center().angle_to(turn.geom.last_pt()))
> 0.0
{
1.0
} else {
-1.0
};
if l1.width == l2.width {
// When two sidewalks or two shoulders meet, use the turn geometry to create some
// nice rounding.
let width = l1.width;
let shift = dir * l1.width / 2.0;
if let Some(poly) = (|| {
let mut pts = turn.geom.shift_left(width / 2.0).ok()?.into_points();
pts.push(l2.first_line().shift_left(width / 2.0).pt1());
pts.push(l2.first_line().shift_right(width / 2.0).pt1());
let mut pts = turn.geom.shift_either_direction(-shift).ok()?.into_points();
pts.push(l2.end_line(i.id).shift_either_direction(shift).pt2());
pts.push(l2.end_line(i.id).shift_either_direction(-shift).pt2());
pts.extend(
turn.geom
.shift_right(width / 2.0)
.shift_either_direction(shift)
.ok()?
.reversed()
.into_points(),
);
pts.push(l1.last_line().shift_right(width / 2.0).pt2());
pts.push(l1.last_line().shift_left(width / 2.0).pt2());
pts.push(l1.end_line(i.id).shift_either_direction(shift).pt2());
pts.push(l1.end_line(i.id).shift_either_direction(-shift).pt2());
pts.push(pts[0]);
// Many resulting shapes aren't valid rings, but we can still triangulate them.
Some(Polygon::buggy_new(pts))
@ -304,10 +310,18 @@ pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
} else {
// When a sidewalk and a shoulder meet, use a simpler shape to connect them.
let mut pts = vec![
l2.first_line().shift_left(l2.width / 2.0).pt1(),
l2.first_line().shift_right(l2.width / 2.0).pt1(),
l1.last_line().shift_right(l1.width / 2.0).pt2(),
l1.last_line().shift_left(l1.width / 2.0).pt2(),
l2.end_line(i.id)
.shift_either_direction(dir * l2.width / 2.0)
.pt2(),
l2.end_line(i.id)
.shift_either_direction(-dir * l2.width / 2.0)
.pt2(),
l1.end_line(i.id)
.shift_either_direction(-dir * l1.width / 2.0)
.pt2(),
l1.end_line(i.id)
.shift_either_direction(dir * l1.width / 2.0)
.pt2(),
];
pts.push(pts[0]);
if let Ok(ring) = Ring::new(pts) {
@ -332,20 +346,26 @@ fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
for turn in &i.turns {
if turn.turn_type == TurnType::SharedSidewalkCorner {
// Avoid double-rendering
if map.get_l(turn.id.src).dst_i != i.id {
continue;
}
let dir = if turn
.geom
.first_pt()
.angle_to(i.polygon.center())
.simple_shortest_rotation_towards(
turn.geom.first_pt().angle_to(turn.geom.last_pt()),
)
> 0.0
{
1.0
} else {
-1.0
};
let l1 = map.get_l(turn.id.src);
let l2 = map.get_l(turn.id.dst);
if l1.width == l2.width {
// When two sidewalks or two shoulders meet, use the turn geometry to create some
// nice rounding.
let mut width = shift(l1.width);
if map.get_config().driving_side == DrivingSide::Right {
width *= -1.0;
}
let width = dir * shift(l1.width);
if let Some(pl) = (|| {
let mut pts = turn.geom.shift_either_direction(width).ok()?.into_points();
@ -354,15 +374,18 @@ fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
// this causes "zig-zaggy" artifacts. The approx_eq check helps some (but not
// all) of those cases, but sometimes introduces visual "gaps". This still
// needs more work.
let first_line = l2.first_line().shift_either_direction(width);
if !pts.last().unwrap().approx_eq(first_line.pt1(), thickness) {
pts.push(first_line.pt1());
pts.push(first_line.unbounded_dist_along(thickness));
let first_line = l2.end_line(i.id).shift_either_direction(-width);
if !pts.last().unwrap().approx_eq(first_line.pt2(), thickness) {
pts.push(first_line.pt2());
pts.push(first_line.unbounded_dist_along(first_line.length() - thickness));
}
let last_line = l1.last_line().shift_either_direction(width).reversed();
if !pts[0].approx_eq(last_line.pt1(), thickness) {
pts.insert(0, last_line.pt1());
pts.insert(0, last_line.unbounded_dist_along(thickness));
let last_line = l1.end_line(i.id).shift_either_direction(width);
if !pts[0].approx_eq(last_line.pt2(), thickness) {
pts.insert(0, last_line.pt2());
pts.insert(
0,
last_line.unbounded_dist_along(last_line.length() - thickness),
);
}
PolyLine::deduping_new(pts).ok()
})() {
@ -370,22 +393,17 @@ fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
}
} else {
// When a sidewalk and a shoulder meet, use a simpler shape to connect them.
let direction = if map.get_config().driving_side == DrivingSide::Right {
-1.0
} else {
1.0
};
let last_line = l1
.last_line()
.shift_either_direction(direction * shift(l1.width));
let first_line = l2
.first_line()
.shift_either_direction(direction * shift(l2.width));
let l1_line = l1
.end_line(i.id)
.shift_either_direction(dir * shift(l1.width));
let l2_line = l2
.end_line(i.id)
.shift_either_direction(-dir * shift(l2.width));
if let Ok(pl) = PolyLine::deduping_new(vec![
last_line.reversed().unbounded_dist_along(thickness),
last_line.pt2(),
first_line.pt1(),
first_line.unbounded_dist_along(thickness),
l1_line.unbounded_dist_along(l1_line.length() - thickness),
l1_line.pt2(),
l2_line.pt2(),
l2_line.unbounded_dist_along(l2_line.length() - thickness),
]) {
curbs.push(pl.make_polygons(thickness));
}

View File

@ -1,4 +1,4 @@
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use anyhow::Result;
use nbez::{Bez3o, BezCurve, Point2d};
@ -219,7 +219,6 @@ fn make_vehicle_turns(i: &Intersection, map: &Map) -> Vec<Turn> {
dst: dst.id,
},
turn_type,
other_crosswalk_ids: BTreeSet::new(),
geom,
});
}

View File

@ -1,172 +1,126 @@
use std::collections::BTreeSet;
use abstutil::wraparound_get;
use geom::{Distance, Line, PolyLine, Pt2D, Ring};
use geom::{Distance, PolyLine, Pt2D, Ring};
use crate::{
Direction, DrivingSide, Intersection, IntersectionID, Lane, LaneID, LaneType, Map, Road, Turn,
TurnID, TurnType,
Direction, DrivingSide, Intersection, IntersectionID, Lane, LaneID, Map, Turn, TurnID, TurnType,
};
/// Generate Crosswalk and SharedSidewalkCorner (places where two sidewalks directly meet) turns.
/// UnmarkedCrossings are not generated here; another process later "downgrades" crosswalks to
/// unmarked.
/// A complete rewrite of make_walking_turns, which looks at all sidewalks (or lack thereof) in
/// counter-clockwise order around an intersection. Based on adjacency, create a
/// SharedSidewalkCorner or a Crosswalk.
pub fn make_walking_turns(map: &Map, i: &Intersection) -> Vec<Turn> {
if i.merged {
return make_walking_turns_v2(map, i);
}
if i.is_footway(map) {
return make_footway_turns(map, i);
}
let driving_side = map.config.driving_side;
let roads: Vec<&Road> = i
.get_roads_sorted_by_incoming_angle(map)
.into_iter()
.map(|id| map.get_r(id))
.collect();
let mut result: Vec<Turn> = Vec::new();
// Consider all roads in counter-clockwise order. Every road has up to two sidewalks. Gather
// those in order, remembering what roads don't have them.
let mut lanes: Vec<Option<&Lane>> = Vec::new();
let mut sorted_roads = i.get_roads_sorted_by_incoming_angle(map);
// And for left-handed driving, we need to walk around in the opposite order.
if driving_side == DrivingSide::Left {
sorted_roads.reverse();
}
// I'm a bit confused when to do -1 and +1 honestly, but this works in practice. Angle sorting
// may be a little backwards.
let idx_offset = if driving_side == DrivingSide::Right {
-1
for r in sorted_roads {
let road = map.get_r(r);
let mut fwd = None;
let mut back = None;
for l in &road.lanes {
if l.lane_type.is_walkable() {
if l.dir == Direction::Fwd {
fwd = Some(l);
} else {
1
back = Some(l);
}
}
}
let (in_lane, out_lane) = if road.src_i == i.id {
(back, fwd)
} else {
(fwd, back)
};
if i.is_degenerate() {
if let Some(turns) = make_degenerate_crosswalks(map, i.id, roads[0], roads[1]) {
result.extend(turns);
// Don't add None entries for footways even if they only have one lane
if map.get_r(r).is_footway() {
if in_lane.is_some() {
lanes.push(in_lane);
}
// TODO Argh, duplicate logic for SharedSidewalkCorners
for idx1 in 0..roads.len() {
if let Some(l1) = get_sidewalk(map, roads[idx1].incoming_lanes(i.id)) {
if let Some(l2) = get_sidewalk(
map,
wraparound_get(&roads, (idx1 as isize) + idx_offset).outgoing_lanes(i.id),
) {
if l1.last_pt() != l2.first_pt() {
let geom = make_shared_sidewalk_corner(driving_side, i, l1, l2);
result.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.clone(),
});
result.push(Turn {
id: turn_id(i.id, l2.id, l1.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.reversed(),
});
if out_lane.is_some() {
lanes.push(out_lane);
}
}
}
}
return result;
}
if roads.len() == 1 {
if let Some(l1) = get_sidewalk(map, roads[0].incoming_lanes(i.id)) {
if let Some(l2) = get_sidewalk(map, roads[0].outgoing_lanes(i.id)) {
let geom = make_shared_sidewalk_corner(driving_side, i, l1, l2);
result.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.clone(),
});
result.push(Turn {
id: turn_id(i.id, l2.id, l1.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.reversed(),
});
}
}
return result;
}
for idx1 in 0..roads.len() {
if let Some(l1) = get_sidewalk(map, roads[idx1].incoming_lanes(i.id)) {
// Make the crosswalk to the other side
if let Some(l2) = get_sidewalk(map, roads[idx1].outgoing_lanes(i.id)) {
result.extend(
make_crosswalks(i.id, l1, l2, driving_side)
.into_iter()
.flatten(),
);
}
// Find the shared corner
if let Some(l2) = get_sidewalk(
map,
wraparound_get(&roads, (idx1 as isize) + idx_offset).outgoing_lanes(i.id),
) {
if l1.last_pt() != l2.first_pt() {
let geom = make_shared_sidewalk_corner(driving_side, i, l1, l2);
result.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.clone(),
});
result.push(Turn {
id: turn_id(i.id, l2.id, l1.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.reversed(),
});
}
} else if let Some(l2) = get_sidewalk(
map,
wraparound_get(&roads, (idx1 as isize) + idx_offset).incoming_lanes(i.id),
) {
// Adjacent road is missing a sidewalk on the near side, but has one on the far
// side
result.extend(
make_crosswalks(i.id, l1, l2, driving_side)
.into_iter()
.flatten(),
);
} else {
// We may need to add a crosswalk over this intermediate road that has no
// sidewalks at all. There might be a few in the way -- think highway onramps.
// TODO Refactor and loop until we find something to connect it to?
if let Some(l2) = get_sidewalk(
map,
wraparound_get(&roads, (idx1 as isize) + 2 * idx_offset).outgoing_lanes(i.id),
) {
result.extend(
make_crosswalks(i.id, l1, l2, driving_side)
.into_iter()
.flatten(),
);
} else if let Some(l2) = get_sidewalk(
map,
wraparound_get(&roads, (idx1 as isize) + 2 * idx_offset).incoming_lanes(i.id),
) {
result.extend(
make_crosswalks(i.id, l1, l2, driving_side)
.into_iter()
.flatten(),
);
} else if roads.len() > 3 {
if let Some(l2) = get_sidewalk(
map,
wraparound_get(&roads, (idx1 as isize) + 3 * idx_offset)
.outgoing_lanes(i.id),
) {
result.extend(
make_crosswalks(i.id, l1, l2, driving_side)
.into_iter()
.flatten(),
);
lanes.push(in_lane);
lanes.push(out_lane);
}
}
// If there are 0 or 1 sidewalks there are no turns to be made
if lanes.iter().filter(|l| l.is_some()).count() <= 1 {
return Vec::new();
}
// Make sure we start with a sidewalk.
while lanes[0].is_none() {
lanes.rotate_left(1);
}
let mut result: Vec<Turn> = Vec::new();
let mut from: Option<&Lane> = lanes[0];
let first_from = from.unwrap().id;
let mut adj = true;
for l in lanes.iter().skip(1).chain(lanes.iter()) {
if i.id.0 == 284 {
debug!(
"looking at {:?}. from is {:?}, first_from is {}, adj is {}",
l.map(|l| l.id),
from.map(|l| l.id),
first_from,
adj
);
}
if from.is_none() {
from = *l;
adj = true;
continue;
}
let l1 = from.unwrap();
if l.is_none() {
adj = false;
continue;
}
let l2 = l.unwrap();
if adj && l1.id.road != l2.id.road {
result.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::SharedSidewalkCorner,
geom: make_shared_sidewalk_corner(i, l1, l2),
});
from = Some(l2);
// adj stays true
} else {
// Only make one crosswalk for degenerate intersections
if !(i.is_degenerate() || i.is_deadend())
|| !result.iter().any(|t| t.turn_type == TurnType::Crosswalk)
{
result.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::Crosswalk,
geom: make_crosswalk(i, l1, l2),
});
}
from = Some(l2);
adj = true;
}
// Have we made it all the way around?
if first_from == from.unwrap().id {
break;
}
}
@ -215,285 +169,62 @@ pub fn filter_turns(mut input: Vec<Turn>, map: &Map, i: &Intersection) -> Vec<Tu
input
}
/// A complete rewrite of make_walking_turns, which looks at all sidewalks (or lack thereof) in
/// counter-clockwise order around an intersection. Based on adjacency, create a
/// SharedSidewalkCorner or a Crosswalk.
///
/// TODO This is only used for consolidated intersections right now. Cut over to this completely
/// after fixing problems like:
/// - too many crosswalks at the Boyer roundabout
/// - one centered crosswalk for degenerate intersections
fn make_walking_turns_v2(map: &Map, i: &Intersection) -> Vec<Turn> {
let driving_side = map.config.driving_side;
fn make_crosswalk(i: &Intersection, l1: &Lane, l2: &Lane) -> PolyLine {
let l1_line = l1.end_line(i.id);
let l2_line = l2.end_line(i.id);
// Consider all roads in counter-clockwise order. Every road has up to two sidewalks. Gather
// those in order, remembering what roads don't have them.
let mut lanes: Vec<Option<&Lane>> = Vec::new();
let mut num_sidewalks = 0;
let mut sorted_roads = i.get_roads_sorted_by_incoming_angle(map);
// And for left-handed driving, we need to walk around in the opposite order.
if driving_side == DrivingSide::Left {
sorted_roads.reverse();
}
for r in sorted_roads {
let road = map.get_r(r);
let mut fwd = None;
let mut back = None;
for l in &road.lanes {
if l.lane_type.is_walkable() {
if l.dir == Direction::Fwd {
fwd = Some(l);
// Jut out a bit into the intersection, cross over, then jut back in.
// Put degenerate intersection crosswalks in the middle (DEGENERATE_HALF_LENGTH).
PolyLine::deduping_new(vec![
l1_line.pt2(),
l1_line.unbounded_dist_along(
l1_line.length()
+ if i.is_degenerate() {
Distance::const_meters(2.5)
} else {
back = Some(l);
}
}
}
if fwd.is_some() {
num_sidewalks += 1;
}
if back.is_some() {
num_sidewalks += 1;
}
let (in_lane, out_lane) = if road.src_i == i.id {
(back, fwd)
} else {
(fwd, back)
};
lanes.push(in_lane);
lanes.push(out_lane);
}
if num_sidewalks <= 1 {
return Vec::new();
}
// Make sure we start with a sidewalk.
while lanes[0].is_none() {
lanes.rotate_left(1);
}
let mut result: Vec<Turn> = Vec::new();
let mut from: Option<&Lane> = lanes[0];
let first_from = from.unwrap().id;
let mut adj = true;
for l in lanes.iter().skip(1).chain(lanes.iter()) {
if i.id.0 == 284 {
debug!(
"looking at {:?}. from is {:?}, first_from is {}, adj is {}",
l.map(|l| l.id),
from.map(|l| l.id),
first_from,
adj
);
}
if from.is_none() {
from = *l;
adj = true;
continue;
}
let l1 = from.unwrap();
if l.is_none() {
adj = false;
continue;
}
let l2 = l.unwrap();
if adj && l1.id.road != l2.id.road {
// Because of the order we go, have to swap l1 and l2 here. l1 is the outgoing, l2 the
// incoming.
let geom = make_shared_sidewalk_corner(driving_side, i, l2, l1);
result.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom: geom.reversed(),
});
result.push(Turn {
id: turn_id(i.id, l2.id, l1.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom,
});
from = Some(l2);
// adj stays true
} else {
// TODO Just one for degenerate intersections
result.extend(
make_crosswalks(i.id, l1, l2, driving_side)
.into_iter()
.flatten(),
);
from = Some(l2);
adj = true;
}
// Have we made it all the way around?
if first_from == from.unwrap().id {
break;
}
}
result
}
/// At an intersection of footpaths only, just generate a turn between every pair of lanes.
fn make_footway_turns(map: &Map, i: &Intersection) -> Vec<Turn> {
let lanes = i
.incoming_lanes
.iter()
.chain(&i.outgoing_lanes)
.filter_map(|l| {
let l = map.get_l(*l);
if l.is_walkable() {
Some(l)
} else {
None
}
})
.collect::<Vec<&Lane>>();
let mut results = Vec::new();
for l1 in &lanes {
for l2 in &lanes {
if l1.id == l2.id {
continue;
}
let maybe_geom = PolyLine::new(vec![l1.endpoint(i.id), l2.endpoint(i.id)]);
let geom = maybe_geom.unwrap_or_else(|_| {
// TODO Gross! After improving intersection geometry where these cases are
// happening, if this still happens, maybe it's time to make turn geometry be
// optional.
PolyLine::must_new(vec![l1.endpoint(i.id), l1.endpoint(i.id).offset(0.1, 0.1)])
});
results.push(Turn {
id: turn_id(i.id, l1.id, l2.id),
turn_type: TurnType::SharedSidewalkCorner,
other_crosswalk_ids: BTreeSet::new(),
geom,
});
}
}
results
}
fn make_crosswalks(
i: IntersectionID,
l1: &Lane,
l2: &Lane,
driving_side: DrivingSide,
) -> Option<Vec<Turn>> {
let l1_pt = l1.endpoint(i);
let l2_pt = l2.endpoint(i);
// This is one of those uncomfortably "trial-and-error" kind of things.
let mut direction = if (l1.dst_i == i) == (l2.dst_i == i) {
-1.0
} else {
1.0
};
if driving_side == DrivingSide::Left {
direction *= -1.0;
}
// Jut out a bit into the intersection, cross over, then jut back in. Assumes sidewalks are the
// same width.
let line = Line::new(l1_pt, l2_pt)?.shift_either_direction(direction * l1.width / 2.0);
let geom_fwds = PolyLine::deduping_new(vec![l1_pt, line.pt1(), line.pt2(), l2_pt]).ok()?;
Some(vec![
Turn {
id: turn_id(i, l1.id, l2.id),
turn_type: TurnType::Crosswalk,
other_crosswalk_ids: vec![turn_id(i, l2.id, l1.id)].into_iter().collect(),
geom: geom_fwds.clone(),
l1.width / 2.0
},
Turn {
id: turn_id(i, l2.id, l1.id),
turn_type: TurnType::Crosswalk,
other_crosswalk_ids: vec![turn_id(i, l1.id, l2.id)].into_iter().collect(),
geom: geom_fwds.reversed(),
),
l2_line.unbounded_dist_along(
l2_line.length()
+ if i.is_degenerate() {
Distance::const_meters(2.5)
} else {
l2.width / 2.0
},
),
l2_line.pt2(),
])
}
// Only one physical crosswalk for degenerate intersections, right in the middle.
fn make_degenerate_crosswalks(
map: &Map,
i: IntersectionID,
r1: &Road,
r2: &Road,
) -> Option<Vec<Turn>> {
let l1_in = get_sidewalk(map, r1.incoming_lanes(i))?;
let l1_out = get_sidewalk(map, r1.outgoing_lanes(i))?;
let l2_in = get_sidewalk(map, r2.incoming_lanes(i))?;
let l2_out = get_sidewalk(map, r2.outgoing_lanes(i))?;
let pt1 = Line::new(l1_in.last_pt(), l2_out.first_pt())?.percent_along(0.5)?;
let pt2 = Line::new(l1_out.first_pt(), l2_in.last_pt())?.percent_along(0.5)?;
if pt1 == pt2 {
return None;
}
let mut all_ids = BTreeSet::new();
all_ids.insert(turn_id(i, l1_in.id, l1_out.id));
all_ids.insert(turn_id(i, l1_out.id, l1_in.id));
all_ids.insert(turn_id(i, l2_in.id, l2_out.id));
all_ids.insert(turn_id(i, l2_out.id, l2_in.id));
Some(
vec![
Turn {
id: turn_id(i, l1_in.id, l1_out.id),
turn_type: TurnType::Crosswalk,
other_crosswalk_ids: all_ids.clone(),
geom: PolyLine::deduping_new(vec![l1_in.last_pt(), pt1, pt2, l1_out.first_pt()])
.ok()?,
},
Turn {
id: turn_id(i, l1_out.id, l1_in.id),
turn_type: TurnType::Crosswalk,
other_crosswalk_ids: all_ids.clone(),
geom: PolyLine::deduping_new(vec![l1_out.first_pt(), pt2, pt1, l1_in.last_pt()])
.ok()?,
},
Turn {
id: turn_id(i, l2_in.id, l2_out.id),
turn_type: TurnType::Crosswalk,
other_crosswalk_ids: all_ids.clone(),
geom: PolyLine::deduping_new(vec![l2_in.last_pt(), pt2, pt1, l2_out.first_pt()])
.ok()?,
},
Turn {
id: turn_id(i, l2_out.id, l2_in.id),
turn_type: TurnType::Crosswalk,
other_crosswalk_ids: all_ids,
geom: PolyLine::deduping_new(vec![l2_out.first_pt(), pt1, pt2, l2_in.last_pt()])
.ok()?,
},
]
.into_iter()
.map(|mut t| {
t.other_crosswalk_ids.remove(&t.id);
t
})
.collect(),
)
.unwrap_or_else(|_| PolyLine::unchecked_new(vec![l1.endpoint(i.id), l2.endpoint(i.id)]))
}
// TODO This doesn't handle sidewalk/shoulder transitions
fn make_shared_sidewalk_corner(
driving_side: DrivingSide,
i: &Intersection,
l1: &Lane,
l2: &Lane,
) -> PolyLine {
let baseline = PolyLine::must_new(vec![l1.last_pt(), l2.first_pt()]);
fn make_shared_sidewalk_corner(i: &Intersection, l1: &Lane, l2: &Lane) -> PolyLine {
// This may produce a polyline with two identical points. Nothing better to do here.
let baseline = PolyLine::unchecked_new(vec![l1.endpoint(i.id), l2.endpoint(i.id)]);
// Is point2 counter-clockwise of point1?
let dir = if i
.polygon
.center()
.angle_to(l1.endpoint(i.id))
.simple_shortest_rotation_towards(i.polygon.center().angle_to(l2.endpoint(i.id)))
> 0.0
{
1.0
} else {
-1.0
};
// Find all of the points on the intersection polygon between the two sidewalks. Assumes
// sidewalks are the same length.
let corner1 = l1.last_line().shift_right(l1.width / 2.0).pt2();
let corner2 = l2.first_line().shift_right(l2.width / 2.0).pt1();
let corner1 = l1
.end_line(i.id)
.shift_either_direction(dir * l1.width / 2.0)
.pt2();
let corner2 = l2
.end_line(i.id)
.shift_either_direction(-dir * l2.width / 2.0)
.pt2();
// TODO Something like this will be MUCH simpler and avoid going around the long way sometimes.
if false {
@ -504,12 +235,15 @@ fn make_shared_sidewalk_corner(
// The order of the points here seems backwards, but it's because we scan from corner2
// to corner1 below.
let mut pts_between = vec![l2.first_pt()];
let mut pts_between = vec![l2.endpoint(i.id)];
// Intersection polygons are constructed in clockwise order, so do corner2 to corner1.
let mut i_pts = i.polygon.points().clone();
if driving_side == DrivingSide::Left {
if dir < 0.0 {
i_pts.reverse();
}
if let Some(pts) = Pt2D::find_pts_between(&i_pts, corner2, corner1, Distance::meters(0.5)) {
let mut deduped = pts;
deduped.dedup();
@ -528,7 +262,9 @@ fn make_shared_sidewalk_corner(
return baseline;
}
if let Ok(pl) = PolyLine::must_new(deduped).shift_right(l1.width.min(l2.width) / 2.0) {
if let Ok(pl) = PolyLine::must_new(deduped)
.shift_either_direction(dir * l1.width.min(l2.width) / 2.0)
{
pts_between.extend(pl.points());
} else {
warn!(
@ -540,7 +276,7 @@ fn make_shared_sidewalk_corner(
}
}
}
pts_between.push(l1.last_pt());
pts_between.push(l1.endpoint(i.id));
pts_between.reverse();
// Pretty big smoothing; I'm observing funky backtracking about 0.5m long.
let mut final_pts = Pt2D::approx_dedupe(pts_between.clone(), Distance::meters(1.0));
@ -554,16 +290,17 @@ fn make_shared_sidewalk_corner(
}
// The last point might be removed as a duplicate, but we want the start/end to exactly match
// up at least.
if *final_pts.last().unwrap() != l2.first_pt() {
if *final_pts.last().unwrap() != l2.endpoint(i.id) {
final_pts.pop();
final_pts.push(l2.first_pt());
final_pts.push(l2.endpoint(i.id));
}
if abstutil::contains_duplicates(
&final_pts
.iter()
.map(|pt| pt.to_hashable())
.collect::<Vec<_>>(),
) {
) || final_pts.len() < 2
{
warn!(
"SharedSidewalkCorner between {} and {} has weird duplicate geometry, so just doing \
straight line",
@ -588,12 +325,3 @@ fn make_shared_sidewalk_corner(
fn turn_id(parent: IntersectionID, src: LaneID, dst: LaneID) -> TurnID {
TurnID { parent, src, dst }
}
fn get_sidewalk(map: &Map, children: Vec<(LaneID, LaneType)>) -> Option<&Lane> {
for (id, lt) in children {
if lt.is_walkable() {
return Some(map.get_l(id));
}
}
None
}

View File

@ -329,24 +329,15 @@ impl Stage {
}
pub fn edit_movement(&mut self, g: &Movement, pri: TurnPriority) {
let mut ids = vec![g.id];
if g.turn_type.pedestrian_crossing() {
ids.push(MovementID {
from: g.id.to,
to: g.id.from,
parent: g.id.parent,
crosswalk: true,
});
self.enforce_minimum_crosswalk_time(g);
}
for id in ids {
self.protected_movements.remove(&id);
self.yield_movements.remove(&id);
self.protected_movements.remove(&g.id);
self.yield_movements.remove(&g.id);
if pri == TurnPriority::Protected {
self.protected_movements.insert(id);
self.protected_movements.insert(g.id);
} else if pri == TurnPriority::Yield {
self.yield_movements.insert(id);
}
self.yield_movements.insert(g.id);
}
}
pub fn enforce_minimum_crosswalk_time(&mut self, movement: &Movement) {

View File

@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::fmt;
use serde::{Deserialize, Serialize};
@ -79,9 +79,6 @@ pub struct Turn {
// TODO Some turns might not actually have geometry. Currently encoded by two equal points.
// Represent more directly?
pub geom: PolyLine,
/// Empty except for TurnType::Crosswalk and UnmarkedCrossing. Usually just one other ID,
/// except for the case of 4 duplicates at a degenerate intersection.
pub other_crosswalk_ids: BTreeSet<TurnID>,
}
impl Turn {