use std::collections::{BTreeMap, BTreeSet};
use anyhow::Result;
use abstutil::wraparound_get;
use geom::{Circle, Distance, Line, PolyLine, Polygon, Pt2D, Ring, EPSILON_DIST};
use crate::make::initial::Road;
use crate::osm;
use crate::raw::OriginalRoad;
const DEGENERATE_INTERSECTION_HALF_LENGTH: Distance = Distance::const_meters(2.5);
pub fn intersection_polygon(
intersection_id: osm::NodeID,
intersection_roads: BTreeSet<OriginalRoad>,
roads: &mut BTreeMap<OriginalRoad, Road>,
merged: bool,
) -> Result<(Polygon, Vec<(String, Polygon)>)> {
if intersection_roads.is_empty() {
panic!("{} has no roads", intersection_id);
}
let mut lines: Vec<(OriginalRoad, Pt2D, PolyLine, PolyLine)> = Vec::new();
let mut intersection_center = Pt2D::new(0.0, 0.0);
for id in &intersection_roads {
let r = &roads[id];
let pl = if r.src_i == intersection_id {
r.trimmed_center_pts.reversed()
} else if r.dst_i == intersection_id {
r.trimmed_center_pts.clone()
} else {
panic!(
"Incident road {} doesn't have an endpoint at {}",
id, intersection_id
);
};
let pl_normal = pl.shift_right(r.half_width)?;
let pl_reverse = pl.shift_left(r.half_width)?;
lines.push((*id, pl.first_pt(), pl_normal, pl_reverse));
intersection_center = pl.last_pt();
}
lines.sort_by_key(|(_, pt, _, _)| pt.angle_to(intersection_center).normalized_degrees() as i64);
if lines.len() == 1 {
return deadend(roads, intersection_id, &lines);
}
let rollback = lines
.iter()
.map(|(r, _, _, _)| (*r, roads[r].trimmed_center_pts.clone()))
.collect::<Vec<_>>();
if let Some(result) = on_off_ramp(roads, intersection_id, lines.clone()) {
Ok(result)
} else {
for (r, trimmed_center_pts) in rollback {
roads.get_mut(&r).unwrap().trimmed_center_pts = trimmed_center_pts;
}
let (result, debug) = generalized_trim_back(roads, intersection_id, &lines)?;
if merged {
if let Some(fixed) = convex_hull_merged_intersection(
result.clone(),
intersection_id,
intersection_roads,
roads,
) {
return Ok((fixed, debug));
}
}
Ok((result, debug))
}
}
fn generalized_trim_back(
roads: &mut BTreeMap<OriginalRoad, Road>,
i: osm::NodeID,
lines: &[(OriginalRoad, Pt2D, PolyLine, PolyLine)],
) -> Result<(Polygon, Vec<(String, Polygon)>)> {
let mut debug = Vec::new();
let mut road_lines: Vec<(OriginalRoad, PolyLine)> = Vec::new();
for (r, _, pl1, pl2) in lines {
road_lines.push((*r, pl1.clone()));
road_lines.push((*r, pl2.clone()));
if false {
debug.push((
format!("{} fwd", r.osm_way_id),
pl1.make_polygons(Distance::meters(1.0)),
));
debug.push((
format!("{} back", r.osm_way_id),
pl2.make_polygons(Distance::meters(1.0)),
));
}
}
let mut new_road_centers: BTreeMap<OriginalRoad, PolyLine> = BTreeMap::new();
for (r1, pl1) in &road_lines {
let road_center = if roads[r1].dst_i == i {
roads[r1].trimmed_center_pts.clone()
} else {
roads[r1].trimmed_center_pts.reversed()
};
let mut shortest_center =
if road_center.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH + 3.0 * EPSILON_DIST {
road_center.exact_slice(
Distance::ZERO,
road_center.length() - DEGENERATE_INTERSECTION_HALF_LENGTH,
)
} else {
road_center.clone()
};
for (r2, pl2) in &road_lines {
if r1 == r2 {
continue;
}
let same_endpoints = {
let ii1 = roads[r1].src_i;
let ii2 = roads[r1].dst_i;
let ii3 = roads[r2].src_i;
let ii4 = roads[r2].dst_i;
(ii1 == ii3 && ii2 == ii4) || (ii1 == ii4 && ii2 == ii3)
};
let (use_pl1, use_pl2): (PolyLine, PolyLine) = if same_endpoints {
(pl1.second_half(), pl2.second_half())
} else {
(pl1.clone(), pl2.clone())
};
if use_pl1 == use_pl2 {
bail!(
"{} and {} have overlapping segments. You likely need to fix OSM and make the \
two ways meet at exactly one node.",
r1,
r2
);
}
if let Some((hit, angle)) = use_pl1.reversed().intersection(&use_pl2) {
let perp = Line::must_new(
hit,
hit.project_away(Distance::meters(1.0), angle.rotate_degs(90.0)),
)
.infinite();
if let Some(trimmed) = road_center
.reversed()
.intersection_infinite(&perp)
.and_then(|trim_to| road_center.get_slice_ending_at(trim_to))
{
if trimmed.length() < shortest_center.length() {
shortest_center = trimmed;
}
} else {
warn!(
"{} and {} hit, but the perpendicular never hit the original center line, \
or the trimmed thing is empty",
r1, r2
);
}
}
}
let new_center = if roads[r1].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);
}
}
let mut endpoints: Vec<Pt2D> = Vec::new();
for idx in 0..lines.len() as isize {
let (id, _, fwd_pl, back_pl) = wraparound_get(&lines, idx);
let (_adj_back_id, _, adj_back_pl, _) = wraparound_get(&lines, idx + 1);
let (_adj_fwd_id, _, _, adj_fwd_pl) = wraparound_get(&lines, idx - 1);
roads.get_mut(&id).unwrap().trimmed_center_pts = new_road_centers[&id].clone();
let r = &roads[&id];
if fwd_pl.length() >= EPSILON_DIST * 3.0 && adj_fwd_pl.length() >= EPSILON_DIST * 3.0 {
if let Some((hit, _)) = fwd_pl.second_half().intersection(&adj_fwd_pl.second_half()) {
endpoints.push(hit);
}
} else {
warn!(
"Excluding collision between original polylines of {} and something, because \
stuff's too short",
id
);
}
if r.dst_i == i {
endpoints.push(r.trimmed_center_pts.shift_right(r.half_width)?.last_pt());
endpoints.push(r.trimmed_center_pts.shift_left(r.half_width)?.last_pt());
} else {
endpoints.push(r.trimmed_center_pts.shift_left(r.half_width)?.first_pt());
endpoints.push(r.trimmed_center_pts.shift_right(r.half_width)?.first_pt());
}
if back_pl.length() >= EPSILON_DIST * 3.0 && adj_back_pl.length() >= EPSILON_DIST * 3.0 {
if let Some((hit, _)) = back_pl
.second_half()
.intersection(&adj_back_pl.second_half())
{
endpoints.push(hit);
}
} else {
warn!(
"Excluding collision between original polylines of {} and something, because \
stuff's too short",
id
);
}
}
let main_result = close_off_polygon(Pt2D::approx_dedupe(endpoints, Distance::meters(0.1)));
let mut deduped = main_result.clone();
deduped.pop();
deduped.sort_by_key(|pt| pt.to_hashable());
deduped = Pt2D::approx_dedupe(deduped, Distance::meters(0.1));
let center = Pt2D::center(&deduped);
deduped.sort_by_key(|pt| pt.angle_to(center).normalized_degrees() as i64);
deduped = Pt2D::approx_dedupe(deduped, Distance::meters(0.1));
deduped = close_off_polygon(deduped);
if main_result.len() == deduped.len() {
Ok((Ring::must_new(main_result).into_polygon(), debug))
} else {
warn!(
"{}'s polygon has weird repeats, forcibly removing points",
i
);
Ok((Ring::must_new(deduped).into_polygon(), debug))
}
}
fn deadend(
roads: &mut BTreeMap<OriginalRoad, Road>,
i: osm::NodeID,
lines: &[(OriginalRoad, Pt2D, PolyLine, PolyLine)],
) -> Result<(Polygon, Vec<(String, Polygon)>)> {
let len = DEGENERATE_INTERSECTION_HALF_LENGTH * 4.0;
let (id, _, mut pl_a, mut pl_b) = lines[0].clone();
pl_a = pl_a.extend_to_length(len + 1.5 * DEGENERATE_INTERSECTION_HALF_LENGTH);
pl_b = pl_b.extend_to_length(len + 1.5 * DEGENERATE_INTERSECTION_HALF_LENGTH);
let r = roads.get_mut(&id).unwrap();
let len_with_buffer = len + 3.0 * EPSILON_DIST;
let trimmed = if r.trimmed_center_pts.length() >= len_with_buffer {
if r.src_i == i {
r.trimmed_center_pts = r
.trimmed_center_pts
.exact_slice(len, r.trimmed_center_pts.length());
} else {
r.trimmed_center_pts = r
.trimmed_center_pts
.exact_slice(Distance::ZERO, r.trimmed_center_pts.length() - len);
}
r.trimmed_center_pts.clone()
} else if r.src_i == i {
r.trimmed_center_pts.extend_to_length(len_with_buffer)
} else {
r.trimmed_center_pts
.reversed()
.extend_to_length(len_with_buffer)
.reversed()
};
let mut endpts = vec![pl_b.last_pt(), pl_a.last_pt()];
if r.dst_i == i {
endpts.push(trimmed.shift_right(r.half_width)?.last_pt());
endpts.push(trimmed.shift_left(r.half_width)?.last_pt());
} else {
endpts.push(trimmed.shift_left(r.half_width)?.first_pt());
endpts.push(trimmed.shift_right(r.half_width)?.first_pt());
}
endpts.dedup();
Ok((
Ring::must_new(close_off_polygon(endpts)).into_polygon(),
Vec::new(),
))
}
fn close_off_polygon(mut pts: Vec<Pt2D>) -> Vec<Pt2D> {
if pts.last().unwrap().approx_eq(pts[0], Distance::meters(0.1)) {
pts.pop();
}
pts.push(pts[0]);
pts
}
struct Piece {
id: OriginalRoad,
left: PolyLine,
center: PolyLine,
right: PolyLine,
}
fn on_off_ramp(
roads: &mut BTreeMap<OriginalRoad, Road>,
i: osm::NodeID,
lines: Vec<(OriginalRoad, Pt2D, PolyLine, PolyLine)>,
) -> Option<(Polygon, Vec<(String, Polygon)>)> {
if lines.len() != 3 {
return None;
}
let mut ok = false;
for (r, _, _, _) in &lines {
if roads[r].osm_tags.is_any(
osm::HIGHWAY,
vec![
"motorway",
"motorway_link",
"primary_link",
"secondary_link",
"tertiary_link",
"trunk_link",
],
) {
ok = true;
break;
}
}
if !ok {
return None;
}
let mut debug = Vec::new();
let mut pieces = Vec::new();
for (id, _, right, left) in lines {
let r = &roads[&id];
let center = if r.dst_i == i {
r.trimmed_center_pts.clone()
} else {
r.trimmed_center_pts.reversed()
};
pieces.push(Piece {
id,
left,
center,
right,
});
}
pieces.sort_by_key(|r| (roads[&r.id].half_width, r.id.i2 == i));
let thick1 = pieces.pop().unwrap();
let thick2 = pieces.pop().unwrap();
let thin = pieces.pop().unwrap();
let mut best_hit: Option<(PolyLine, PolyLine, OriginalRoad)> = None;
for &thin_pl in &[&thin.left, &thin.right] {
for &thick in &[&thick1, &thick2] {
for &thick_pl in &[&thick.left, &thick.right] {
if thin_pl == thick_pl {
return None;
}
if let Some((hit, angle)) = thin_pl.intersection(thick_pl) {
let perp = Line::must_new(
hit,
hit.project_away(Distance::meters(1.0), angle.rotate_degs(90.0)),
)
.infinite();
let trimmed_thin = thin
.center
.reversed()
.intersection_infinite(&perp)
.and_then(|trim_to| thin.center.get_slice_ending_at(trim_to))?;
let (_, angle) = thick_pl.dist_along_of_point(hit)?;
let perp = Line::must_new(
hit,
hit.project_away(Distance::meters(1.0), angle.rotate_degs(90.0)),
)
.infinite();
let trimmed_thick = thick
.center
.reversed()
.intersection_infinite(&perp)
.and_then(|trim_to| thick.center.get_slice_ending_at(trim_to))?;
if false {
debug.push((
"1".to_string(),
Circle::new(hit, Distance::meters(3.0)).to_polygon(),
));
debug.push((
"2".to_string(),
Circle::new(trimmed_thin.last_pt(), Distance::meters(3.0)).to_polygon(),
));
debug.push((
"3".to_string(),
Circle::new(trimmed_thick.last_pt(), Distance::meters(3.0))
.to_polygon(),
));
}
if best_hit
.as_ref()
.map(|(pl, _, _)| trimmed_thin.length() < pl.length())
.unwrap_or(true)
{
best_hit = Some((trimmed_thin, trimmed_thick, thick.id));
}
}
}
}
}
{
let (mut trimmed_thin, mut trimmed_thick, thick_id) = best_hit?;
if roads[&thin.id].dst_i != i {
trimmed_thin = trimmed_thin.reversed();
}
roads.get_mut(&thin.id).unwrap().trimmed_center_pts = trimmed_thin;
let extra = if roads[&thick_id].dst_i == i {
roads[&thick_id]
.trimmed_center_pts
.get_slice_starting_at(trimmed_thick.last_pt())?
} else {
trimmed_thick = trimmed_thick.reversed();
roads[&thick_id]
.trimmed_center_pts
.get_slice_ending_at(trimmed_thick.first_pt())?
.reversed()
};
roads.get_mut(&thick_id).unwrap().trimmed_center_pts = trimmed_thick;
if extra.length() <= 2.0 * DEGENERATE_INTERSECTION_HALF_LENGTH + 3.0 * EPSILON_DIST {
return None;
}
let extra = extra.exact_slice(2.0 * DEGENERATE_INTERSECTION_HALF_LENGTH, extra.length());
let other = roads
.get_mut(if thick1.id == thick_id {
&thick2.id
} else {
&thick1.id
})
.unwrap();
if other.dst_i == i {
other.trimmed_center_pts = other
.trimmed_center_pts
.clone()
.extend(extra.reversed())
.ok()?;
} else {
other.trimmed_center_pts = extra.extend(other.trimmed_center_pts.clone()).ok()?;
}
}
let mut endpoints = Vec::new();
for &id in &[thin.id, thick1.id, thick2.id] {
let r = &roads[&id];
if r.dst_i == i {
endpoints.push(
r.trimmed_center_pts
.shift_right(r.half_width)
.ok()?
.last_pt(),
);
endpoints.push(
r.trimmed_center_pts
.shift_left(r.half_width)
.ok()?
.last_pt(),
);
} else {
endpoints.push(
r.trimmed_center_pts
.shift_left(r.half_width)
.ok()?
.first_pt(),
);
endpoints.push(
r.trimmed_center_pts
.shift_right(r.half_width)
.ok()?
.first_pt(),
);
}
}
endpoints.sort_by_key(|pt| pt.to_hashable());
endpoints.dedup();
let center = Pt2D::center(&endpoints);
endpoints.sort_by_key(|pt| pt.angle_to(center).normalized_degrees() as i64);
endpoints.dedup();
Some((
Ring::must_new(close_off_polygon(endpoints)).into_polygon(),
debug,
))
}
fn convex_hull_merged_intersection(
input: Polygon,
intersection_id: osm::NodeID,
intersection_roads: BTreeSet<OriginalRoad>,
roads: &BTreeMap<OriginalRoad, Road>,
) -> Option<Polygon> {
let candidate = Polygon::convex_hull(vec![input]);
let candidate_ring = candidate.clone().into_ring();
for id in intersection_roads {
let r = &roads[&id];
let trimmed_endpt = if r.src_i == intersection_id {
r.trimmed_center_pts.first_pt()
} else {
r.trimmed_center_pts.last_pt()
};
if candidate.contains_pt(trimmed_endpt) && !candidate_ring.contains_pt(trimmed_endpt) {
return None;
}
}
Some(candidate)
}