a much simpler, general approach for intersection polygons. needs a

little work, but it replaces lots of other code.

disabled, but adding a few more cases to the list of manually merged short roads
This commit is contained in:
Dustin Carlino 2019-01-18 22:15:24 -08:00
parent b279e37bab
commit e403a6388d
4 changed files with 123 additions and 308 deletions

View File

@ -2,18 +2,12 @@
## Geometry
- first, stop doing make_old_polygon entirely. figure out the cases where make_new_polygon fails.
- automatically find problems
- for every road band, find the two endpoints. make sure they
exactly match one edge of the intersection polygon.
- more general solver... for merged intersections, just take
perp endpoints of stuff if they dont touch anything else.
combination of trimming back road centers and taking perpendiculars.
- maybe oneway shifting is wrong? do those OSM pts represent the center of the one-way?
- degenerate-2's should only have one crosswalk
- then make them thinner
- generalized_trim_back
- breaks down when we have jagged lane endings due to polyline shift angle correction
- definitely wind up with some extra stuff in the polygon... including the final hit probably helps
- sometimes a lane polyline hits the perpendicular of a trimmed road! trim a bit further to handle that?
- some sidewalk corners are too eager now
- if some centers dont change enough, trim them back a little extra. ex: montlake bridge
- handle small roads again somehow?
- what's correct for 14th and e boston? if we had less lanes there, would it help?
@ -30,6 +24,9 @@
- model U-turns
- degenerate-2's should only have one crosswalk
- then make them thinner
- ped paths through sidewalk corners are totally broken
- figure out what to do about yellow center lines

View File

@ -66,7 +66,7 @@ fn tooltip_lines(obj: ID, ctx: &Ctx) -> Text {
.get("name")
.unwrap_or(&"???".to_string())
.to_string(),
Some(Color::BLUE),
Some(Color::CYAN),
None,
);
txt.add_line(format!("From OSM way {}", r.osm_way_id));
@ -146,6 +146,6 @@ fn styled_kv(txt: &mut Text, tags: &BTreeMap<String, String>) {
for (k, v) in tags {
txt.add_styled_line(k.to_string(), Some(Color::RED), None);
txt.append(" = ".to_string(), None, None);
txt.append(v.to_string(), Some(Color::BLUE), None);
txt.append(v.to_string(), Some(Color::CYAN), None);
}
}

View File

@ -1,8 +1,7 @@
use crate::{Intersection, IntersectionID, Road, RoadID, LANE_THICKNESS};
use abstutil::note;
use abstutil::wraparound_get;
use dimensioned::si;
use geom::{Angle, Line, PolyLine, Pt2D};
use std::collections::HashMap;
use std::marker;
const DEGENERATE_INTERSECTION_HALF_LENGTH: si::Meter<f64> = si::Meter {
@ -50,19 +49,8 @@ pub fn intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) -> Vec<Pt2D
deadend(roads, i.id, &lines)
} else if lines.len() == 2 {
degenerate_twoway(roads, i.id, &lines)
} else if let Some(pts) = make_new_polygon(roads, i.id, &lines) {
pts
} else if let Some(pts) = make_thick_thin_threeway(roads, i.id, &lines) {
pts
} else if let Some(pts) = make_degenerate_threeway(roads, i.id, &lines) {
pts
} else {
note(format!(
"couldnt make new for {} with {} roads",
i.id,
lines.len()
));
make_old_polygon(&lines)
generalized_trim_back(roads, i.id, &lines)
};
// Close off the polygon
@ -134,147 +122,6 @@ fn degenerate_twoway(
}
}
fn make_new_polygon(
roads: &mut Vec<Road>,
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Option<Vec<Pt2D>> {
// Since we might fail halfway through this function, don't actually trim center lines until we
// know we'll succeed.
let mut new_road_centers: Vec<(RoadID, PolyLine)> = Vec::new();
let mut endpoints: Vec<Pt2D> = Vec::new();
// Find the two corners of each road
for idx in 0..lines.len() as isize {
let (id, _, fwd_pl, back_pl) = wraparound_get(&lines, idx);
let (_back_id, _, adj_back_pl, _) = wraparound_get(&lines, idx + 1);
let (_fwd_id, _, _, adj_fwd_pl) = wraparound_get(&lines, idx - 1);
// road_center ends at the intersection.
// TODO This is redoing some work. :\
let road_center = if roads[id.0].dst_i == i {
roads[id.0].center_pts.clone()
} else {
roads[id.0].center_pts.reversed()
};
// If the adjacent polylines don't intersect at all, then we have something like a
// three-way intersection (or maybe just a case where the angles of the two adjacent roads
// are super close). In that case, we only have one corner to choose as a candidate for
// trimming back the road center.
let (fwd_hit, new_center1) = {
if let Some((hit, angle)) = fwd_pl.intersection(adj_fwd_pl) {
// Find where the perpendicular to this corner hits the original line
let perp = Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0)));
let trim_to = road_center.intersection_infinite_line(perp)?;
(Some(hit), Some(road_center.trim_to_pt(trim_to)))
} else {
(None, None)
}
};
let (back_hit, new_center2) = {
if let Some((hit, angle)) = back_pl.intersection(adj_back_pl) {
// Find where the perpendicular to this corner hits the original line
let perp = Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0)));
let trim_to = road_center.intersection_infinite_line(perp)?;
(Some(hit), Some(road_center.trim_to_pt(trim_to)))
} else {
(None, None)
}
};
let shorter_center = match (new_center1, new_center2) {
(Some(c1), Some(c2)) => {
if c1.length() <= c2.length() {
c1
} else {
c2
}
}
(Some(c1), None) => c1,
(None, Some(c2)) => c2,
(None, None) => {
// TODO We might need to revert some shortened road centers!
return None;
}
};
// TODO This is redoing LOTS of work
let r = &mut roads[id.0];
let fwd_width = LANE_THICKNESS * (r.children_forwards.len() as f64);
let back_width = LANE_THICKNESS * (r.children_backwards.len() as f64);
let (width_normal, width_reverse) = if r.src_i == i {
new_road_centers.push((*id, shorter_center.reversed()));
(back_width, fwd_width)
} else {
new_road_centers.push((*id, shorter_center.clone()));
(fwd_width, back_width)
};
let pl_normal = shorter_center.shift_right(width_normal);
let pl_reverse = shorter_center.shift_left(width_reverse);
// Toss in the original corners, so the intersection polygon doesn't cover area not
// originally covered by the thick road bands.
if let Some(hit) = fwd_hit {
endpoints.push(hit);
}
endpoints.push(pl_normal.last_pt());
endpoints.push(pl_reverse.last_pt());
if let Some(hit) = back_hit {
endpoints.push(hit);
}
}
for (id, pl) in new_road_centers {
roads[id.0].center_pts = pl;
}
Some(approx_dedupe(endpoints))
}
fn make_old_polygon(lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>) -> Vec<Pt2D> {
let mut endpoints = Vec::new();
// Look at adjacent pairs of these polylines...
for idx1 in 0..lines.len() as isize {
let idx2 = idx1 + 1;
let (_, _, _, pl1) = wraparound_get(&lines, idx1);
let (_, _, pl2, _) = wraparound_get(&lines, idx2);
// If the two lines are too close in angle, they'll either not hit or even if they do, it
// won't be right.
let angle_diff = (pl1.last_line().angle().opposite().normalized_degrees()
- pl2.last_line().angle().normalized_degrees())
.abs();
// TODO A tuning challenge. :)
if angle_diff > 15.0 {
// The easy case!
if let Some((hit, _)) = pl1.intersection(&pl2) {
endpoints.push(hit);
continue;
}
}
// Use the next adjacent road, doing line to line segment intersection instead.
let inf_line1 = wraparound_get(&lines, idx1 - 1).3.last_line();
if let Some(hit) = pl1.intersection_infinite_line(inf_line1) {
endpoints.push(hit);
} else {
endpoints.push(pl1.last_pt());
}
let inf_line2 = wraparound_get(&lines, idx2 + 1).2.last_line();
if let Some(hit) = pl2.intersection_infinite_line(inf_line2) {
endpoints.push(hit);
} else {
endpoints.push(pl2.last_pt());
}
}
endpoints
}
// Temporary until Pt2D has proper resolution.
fn approx_dedupe(pts: Vec<Pt2D>) -> Vec<Pt2D> {
let mut result: Vec<Pt2D> = Vec::new();
@ -286,141 +133,6 @@ fn approx_dedupe(pts: Vec<Pt2D>) -> Vec<Pt2D> {
result
}
// Does the _a or _b line of any of the roads completely cross another road? This happens often
// when normal roads intersect a highway on/off ramp, or more generally, when the width of one road
// is very different than the others.
fn make_thick_thin_threeway(
roads: &mut Vec<Road>,
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Option<Vec<Pt2D>> {
if lines.len() != 3 {
return None;
}
for thick_idx in 0..3 {
for thick_side in &[true, false] {
let (thick_id, thick_pl) = if *thick_side {
let (id, _, _, pl) = &lines[thick_idx];
(id, pl)
} else {
let (id, _, pl, _) = &lines[thick_idx];
(id, pl)
};
for thin_idx in 0..3 {
if thin_idx == thick_idx {
continue;
}
let (thin_id, _, thin_a, thin_b) = &lines[thin_idx];
if thick_pl.intersection(&thin_a).is_none()
|| thick_pl.intersection(&thin_b).is_none()
{
continue;
}
let thin_pl = if *thick_side { thin_a } else { thin_b };
let (thick_pt1, thick_pt2) =
trim_to_hit(&mut roads[thick_id.0], i, thick_pl, thin_pl);
let (thin_pt1, thin_pt2) = trim_to_hit(&mut roads[thin_id.0], i, thin_pl, thick_pl);
// Leave the other line alone.
let (_, _, other_a, other_b) = &lines[other_idx(thick_idx, thin_idx)];
if *thick_side {
return Some(vec![
thick_pt1,
thick_pt2,
thin_pt1,
thin_pt2,
other_a.last_pt(),
other_b.last_pt(),
]);
} else {
return Some(vec![
thick_pt1,
thick_pt2,
other_a.last_pt(),
other_b.last_pt(),
thin_pt1,
thin_pt2,
]);
}
}
}
}
None
}
// These are helpers for make_thick_thin_threeway.
// Returns the two endpoints for the intersection polygon after trimming, in the (forwards,
// backwards) order.
fn trim_to_hit(
r: &mut Road,
i: IntersectionID,
our_pl: &PolyLine,
other_pl: &PolyLine,
) -> (Pt2D, Pt2D) {
// Find the spot along the road's original center that's perpendicular to the hit. Keep in
// mind our_pl might not be the road's center.
let orig_center = if r.dst_i == i {
r.center_pts.clone()
} else {
r.center_pts.reversed()
};
let (hit, angle) = our_pl.intersection(other_pl).unwrap();
let perp = Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0)));
let trim_to = orig_center.intersection_infinite_line(perp).unwrap();
let new_center = orig_center.trim_to_pt(trim_to);
// TODO Really redoing work. :\
let fwd_width = LANE_THICKNESS * (r.children_forwards.len() as f64);
let back_width = LANE_THICKNESS * (r.children_backwards.len() as f64);
if r.dst_i == i {
r.center_pts = new_center;
(
r.center_pts.shift_right(fwd_width).last_pt(),
r.center_pts.shift_left(back_width).last_pt(),
)
} else {
r.center_pts = new_center.reversed();
(
r.center_pts.shift_left(back_width).first_pt(),
r.center_pts.shift_right(fwd_width).first_pt(),
)
}
}
fn other_idx(idx1: usize, idx2: usize) -> usize {
if idx1 != 0 && idx2 != 0 {
return 0;
}
if idx1 != 1 && idx2 != 1 {
return 1;
}
2
}
fn make_degenerate_threeway(
roads: &mut Vec<Road>,
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Option<Vec<Pt2D>> {
if lines.len() != 3 {
return None;
}
// TODO What if there's a collision farther than the arbitrary length we pick?
make_simple_degenerate(roads, i, lines)
}
fn make_simple_degenerate(
roads: &mut Vec<Road>,
i: IntersectionID,
@ -476,3 +188,86 @@ fn make_simple_degenerate(
}
Some(endpoints)
}
fn generalized_trim_back(
roads: &mut Vec<Road>,
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Vec<Pt2D> {
let mut road_lines: Vec<(RoadID, &PolyLine)> = Vec::new();
for (r, _, pl1, pl2) in lines {
road_lines.push((*r, pl1));
road_lines.push((*r, pl2));
}
let mut new_road_centers: HashMap<RoadID, PolyLine> = HashMap::new();
// Intersect every road's boundary lines with all the other lines
for (r1, pl1) in &road_lines {
// road_center ends at the intersection.
let road_center = if roads[r1.0].dst_i == i {
roads[r1.0].center_pts.clone()
} else {
roads[r1.0].center_pts.reversed()
};
let mut shortest_center = road_center.clone();
for (r2, pl2) in &road_lines {
if r1 == r2 {
continue;
}
if let Some((hit, angle)) = pl1.intersection(pl2) {
// Find where the perpendicular hits the original road line
let perp = Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0)));
// How could something perpendicular to a shifted polyline never hit the original
// polyline?
let trim_to = road_center.intersection_infinite_line(perp).unwrap();
let trimmed = road_center.trim_to_pt(trim_to);
if trimmed.length() < shortest_center.length() {
shortest_center = trimmed;
}
// We could also do the update for r2, but we'll just get to it later.
}
}
let new_center = if roads[r1.0].dst_i == i {
shortest_center
} else {
shortest_center.reversed()
};
if let Some(existing) = new_road_centers.get(r1) {
if new_center.length() < existing.length() {
new_road_centers.insert(*r1, new_center);
}
} else {
new_road_centers.insert(*r1, new_center);
}
}
// After doing all the intersection checks, copy over the new centers. Also shift those centers
// out again to find the endpoints that'll make up the polygon.
let mut endpoints: Vec<Pt2D> = Vec::new();
for (id, center_pts) in new_road_centers {
let mut r = &mut roads[id.0];
r.center_pts = center_pts;
let fwd_width = LANE_THICKNESS * (r.children_forwards.len() as f64);
let back_width = LANE_THICKNESS * (r.children_backwards.len() as f64);
if r.dst_i == i {
endpoints.push(r.center_pts.shift_right(fwd_width).last_pt());
endpoints.push(r.center_pts.shift_left(back_width).last_pt());
} else {
endpoints.push(r.center_pts.shift_right(fwd_width).first_pt());
endpoints.push(r.center_pts.shift_left(back_width).first_pt());
}
}
endpoints = approx_dedupe(endpoints);
let center = Pt2D::center(&endpoints);
endpoints.sort_by_key(|pt| Line::new(center, *pt).angle().normalized_degrees() as i64);
endpoints
}

View File

@ -1,5 +1,5 @@
use crate::raw_data;
use abstutil::{retain_btreemap, Timer};
use abstutil::{note, retain_btreemap, Timer};
use dimensioned::si;
use geom::{PolyLine, Pt2D};
@ -8,12 +8,21 @@ pub fn old_merge_intersections(data: &mut raw_data::Map, _timer: &mut Timer) {
return;
}
// 13th and Lynn
merge(data, raw_data::StableRoadID(311));
// 15th and Howe
merge(data, raw_data::StableRoadID(240));
// 2nd and Interlaken Place
merge(data, raw_data::StableRoadID(91));
// 15th and McGraw
merge(data, raw_data::StableRoadID(59));
//merge(data, raw_data::StableRoadID(59));
// 14th and Boston
merge(data, raw_data::StableRoadID(389));
merge(data, raw_data::StableRoadID(22));
//merge(data, raw_data::StableRoadID(389));
//merge(data, raw_data::StableRoadID(22));
if true {
return;
@ -42,6 +51,20 @@ fn merge(data: &mut raw_data::Map, merge_road: raw_data::StableRoadID) {
// Arbitrarily kill off the first intersection and keep the second one.
let (delete_i, keep_i) = {
let r = data.roads.remove(&merge_road).unwrap();
let gps_bounds = data.get_gps_bounds();
let center_pts = PolyLine::new(
r.points
.iter()
.map(|coord| Pt2D::from_gps(*coord, &gps_bounds).unwrap())
.collect(),
);
note(format!(
"Deleting {}, which has original length {}",
merge_road,
center_pts.length()
));
(r.i1, r.i2)
};
data.intersections.remove(&delete_i);