making turns for crosswalks. had to rethink how turns work slightly.

This commit is contained in:
Dustin Carlino 2018-07-10 14:55:23 -07:00
parent 9ca0682108
commit 9c21dae3d9
10 changed files with 157 additions and 21 deletions

View File

@ -28,8 +28,6 @@
## pedestrians
- calculate turns at crosswalks, see how they conflict
- for now, treat same in control layer
- pedestrians with different speeds, moving bidirectionally on everything
- make them start and end at buildings
- trim the sidewalk path to the edge of a building

View File

@ -186,6 +186,28 @@ https://wiki.openstreetmap.org/wiki/Proposed_features/Street_area
- v1: remember other_side for sidewalks too. draw crosswalks at the beginning AND end of every sidewalk lane.
- do extra drawing in DrawIntersection for now, figure out modeling later.
- alright, directional lanes and turns dont fit sidewalks at all. turn icons
are drawn at one end. the turns-in-an-intersection invariant is broken, since
src and dst dont match up for one side.
- could kind of cheat by doubling lanes for sidewalks and making the geometry
overlap, but this feels like a worse hack. it's very tempting to ditch lanes
and turns for a different way to model sidewalks and crosswalks.
- for now, let's make a distinct road and lane abstraction and plumb that all the way through. see what it'd be like to have some more primitives:
- Intersection
- Building
- Parcel
- Road (undirected bundle)
- Driving/biking Lane (directed)
- Sidewalk (undirected)
- Parking lane (no orientation... or, kind of like a driving lane)
- Turn (directed and not)
but the fact that sidewalks are oriented is actually convenient, it makes it clear that incoming's last pt should be glued to outgoing's first pt.
what if we just add a bit and make turns bidirectional? still express them in the directional way?
if we're looking at turns from a road that's a sidewalk, bake in some extra logic?
## Stop signs
How to depict stop signs? Each driving lane has a priority... asap go or full

View File

@ -120,8 +120,6 @@ impl SelectionState {
}
}
SelectionState::SelectedRoad(id, current_turn_index) => {
let all_turns: Vec<&map_model::Turn> =
map.get_turns_in_intersection(map.get_destination_intersection(id).id);
let relevant_turns = map.get_turns_from_road(id);
if !relevant_turns.is_empty() {
match current_turn_index {
@ -130,10 +128,10 @@ impl SelectionState {
let draw_turn =
draw_map.get_t(relevant_turns[idx % relevant_turns.len()].id);
draw_turn.draw_full(g, cs.get(Colors::Turn));
for map_t in all_turns {
let t = map.get_t(map_t.id);
let draw_t = draw_map.get_t(map_t.id);
for t in map.get_turns_in_intersection(turn.parent) {
if t.conflicts_with(turn) {
let draw_t = draw_map.get_t(t.id);
// TODO should we instead change color_t?
draw_t.draw_icon(g, cs.get(Colors::ConflictingTurn), cs);
}

View File

@ -3,7 +3,7 @@
use aabb_quadtree::geom::{Point, Rect};
use aabb_quadtree::QuadTree;
use geom::{LonLat, Pt2D};
use map_model::{BuildingID, IntersectionID, Map, ParcelID, RoadID, TurnID};
use map_model::{BuildingID, IntersectionID, Map, ParcelID, RoadID, Turn, TurnID};
use plugins::selection::Hider;
use render::building::DrawBuilding;
use render::intersection::DrawIntersection;
@ -36,14 +36,25 @@ impl DrawMap {
let mut turn_to_road_offset: HashMap<TurnID, usize> = HashMap::new();
for r in map.all_roads() {
let mut turns = map.get_turns_from_road(r.id);
// Sort the turn icons by angle.
turns.sort_by_key(|t| t.line.angle().normalized_degrees() as i64);
// Split into two groups, based on the endpoint
let mut pair: (Vec<&Turn>, Vec<&Turn>) = map.get_turns_from_road(r.id)
.iter()
.partition(|t| t.parent == r.dst_i);
for (idx, t) in turns.iter().enumerate() {
// Sort the turn icons by angle.
pair.0
.sort_by_key(|t| t.line.angle().normalized_degrees() as i64);
pair.1
.sort_by_key(|t| t.line.angle().normalized_degrees() as i64);
for (idx, t) in pair.0.iter().enumerate() {
turn_to_road_offset.insert(t.id, idx);
}
for (idx, t) in pair.1.iter().enumerate() {
turn_to_road_offset.insert(t.id, idx);
}
}
assert_eq!(turn_to_road_offset.len(), map.all_turns().len());
let turns: Vec<DrawTurn> = map.all_turns()
.iter()

View File

@ -31,12 +31,13 @@ impl DrawTurn {
let src_pt = turn.line.pt1();
let dst_pt = turn.line.pt2();
let angle = turn.line.angle();
let last_line = map.get_r(turn.src).last_line();
let end_line = map.get_r(turn.src).end_line(turn.parent);
// Start the distance from the intersection
let icon_center = last_line
let icon_center = end_line
.reverse()
.unbounded_dist_along((offset_along_road + 0.5) * TURN_ICON_ARROW_LENGTH * si::M);
let icon_src = icon_center
.project_away(TURN_ICON_ARROW_LENGTH / 2.0, angle.opposite())
.to_vec();

View File

@ -6,4 +6,4 @@ mod turns;
pub(crate) use self::buildings::make_building;
pub(crate) use self::lanes::get_lane_specs;
pub(crate) use self::trim_lines::trim_lines;
pub(crate) use self::turns::make_turns;
pub(crate) use self::turns::{make_crosswalks, make_turns};

View File

@ -42,8 +42,73 @@ pub(crate) fn make_turns(i: &Intersection, m: &Map, turn_id_start: usize) -> Vec
src: *src,
dst: *dst,
line: Line::new(src_r.last_pt(), dst_r.first_pt()),
between_sidewalks: false,
});
}
}
result
}
pub(crate) fn make_crosswalks(i: &Intersection, m: &Map, mut turn_id_start: usize) -> Vec<Turn> {
let mut result = Vec::new();
// TODO dedupe some of this logic render/intersection
// First make all of the crosswalks -- from each incoming and outgoing sidewalk to its other
// side
for id in i.incoming_roads.iter().chain(i.outgoing_roads.iter()) {
let src = m.get_r(*id);
if src.lane_type != LaneType::Sidewalk {
continue;
}
let dst = m.get_r(src.other_side.unwrap());
let id = TurnID(turn_id_start);
turn_id_start += 1;
result.push(Turn {
id,
parent: i.id,
src: src.id,
dst: dst.id,
line: Line::new(src.endpoint(i.id), dst.endpoint(i.id)),
between_sidewalks: true,
});
}
// Then all of the immediate connections onto the shared point
for id1 in i.incoming_roads.iter().chain(i.outgoing_roads.iter()) {
let src = m.get_r(*id1);
if src.lane_type != LaneType::Sidewalk {
continue;
}
let src_pt = src.endpoint(i.id);
for id2 in i.incoming_roads.iter().chain(i.outgoing_roads.iter()) {
if id1 == id2 {
continue;
}
let dst = m.get_r(*id2);
if dst.lane_type != LaneType::Sidewalk {
continue;
}
let dst_pt = dst.endpoint(i.id);
if src_pt != dst_pt {
continue;
}
let id = TurnID(turn_id_start);
turn_id_start += 1;
result.push(Turn {
id,
parent: i.id,
src: src.id,
dst: dst.id,
line: Line::new(src_pt, dst_pt),
between_sidewalks: true,
});
}
}
result
}

View File

@ -128,6 +128,8 @@ impl Map {
for i in &m.intersections {
let turns = make::make_turns(i, &m, m.turns.len());
m.turns.extend(turns);
let crosswalks = make::make_crosswalks(i, &m, m.turns.len());
m.turns.extend(crosswalks);
}
for t in &m.turns {
m.intersections[t.parent.0].turns.push(t.id);
@ -209,14 +211,25 @@ impl Map {
.collect()
}
pub fn get_turns_from_road(&self, id: RoadID) -> Vec<&Turn> {
let i = self.get_destination_intersection(id);
// TODO can't filter on get_turns_in_intersection... winds up being Vec<&&Turn>
i.turns
// The turns may belong to two different intersections!
pub fn get_turns_from_road(&self, r: RoadID) -> Vec<&Turn> {
let road = self.get_r(r);
let mut turns: Vec<&Turn> = self.get_i(road.dst_i)
.turns
.iter()
.map(|t| self.get_t(*t))
.filter(|t| t.src == id)
.collect()
.filter(|t| t.src == r)
.collect();
// Sidewalks are bidirectional
if road.lane_type == LaneType::Sidewalk {
for t in &self.get_i(road.src_i).turns {
let turn = self.get_t(*t);
if turn.src == r {
turns.push(turn);
}
}
}
turns
}
pub fn get_next_roads(&self, from: RoadID) -> Vec<&Road> {

View File

@ -90,6 +90,27 @@ impl Road {
self.lane_center_pts.last_line()
}
pub fn endpoint(&self, i: IntersectionID) -> Pt2D {
if i == self.src_i {
self.first_pt()
} else if i == self.dst_i {
self.last_pt()
} else {
panic!("{} isn't an endpoint of {}", i, self.id);
}
}
// pt2 will be endpoint
pub fn end_line(&self, i: IntersectionID) -> Line {
if i == self.src_i {
self.first_line().reverse()
} else if i == self.dst_i {
self.last_line()
} else {
panic!("{} isn't an endpoint of {}", i, self.id);
}
}
pub fn dist_along(&self, dist_along: si::Meter<f64>) -> (Pt2D, Angle) {
self.lane_center_pts.dist_along(dist_along)
}

View File

@ -20,9 +20,12 @@ impl fmt::Display for TurnID {
#[derive(Debug)]
pub struct Turn {
pub id: TurnID,
// src and dst must both belong to parent. No guarantees that src is incoming and dst is
// outgoing for turns between sidewalks.
pub parent: IntersectionID,
pub src: RoadID,
pub dst: RoadID,
pub(crate) between_sidewalks: bool,
/// GeomTurn stuff
pub line: Line,
@ -36,6 +39,10 @@ impl PartialEq for Turn {
impl Turn {
pub fn conflicts_with(&self, other: &Turn) -> bool {
if self.between_sidewalks && other.between_sidewalks {
return false;
}
if self.line.pt1() == other.line.pt1() {
return false;
}