mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-26 16:02:23 +03:00
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:
parent
b279e37bab
commit
e403a6388d
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user