Skip to content

Commit

Permalink
Refactor the road edge view construct, and use it for a much simpler …
Browse files Browse the repository at this point in the history
…intersection geometry algorithm after trimming road centers. #136

This needs a little more work to keep intersections minimal
  • Loading branch information
dabreegster committed Dec 1, 2022
1 parent 2a115ae commit 682c6ff
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 225 deletions.
214 changes: 32 additions & 182 deletions osm2streets/src/geometry/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::collections::BTreeMap;

use anyhow::Result;

use abstutil::wraparound_get;
use geom::{Circle, Distance, InfiniteLine, PolyLine, Polygon, Pt2D, Ring, EPSILON_DIST};

use super::Results;
use crate::road::RoadEdge;
use crate::{IntersectionID, Road, RoadID};

const DEGENERATE_INTERSECTION_HALF_LENGTH: Distance = Distance::const_meters(2.5);
Expand Down Expand Up @@ -84,9 +84,9 @@ pub fn intersection_polygon(
}

if !trim_roads_for_merging.is_empty() {
pretrimmed_geometry(results, &road_lines)
finalize_intersection_polygon(results, &road_lines)
} else if let Some(result) = on_off_ramp(results.clone(), road_lines.clone()) {
Ok(result)
result
} else {
generalized_trim_back(results, &road_lines)
}
Expand Down Expand Up @@ -235,127 +235,7 @@ fn generalized_trim_back(mut results: Results, input_road_lines: &[RoadLine]) ->
results.roads.get_mut(&id).unwrap().trimmed_center_line = pl;
}

calculate_polygon(results, input_road_lines)
}

fn calculate_polygon(mut results: Results, input_road_lines: &[RoadLine]) -> Result<Results> {
let mut endpoints: Vec<Pt2D> = Vec::new();
for idx in 0..input_road_lines.len() as isize {
let (id, fwd_pl, back_pl) = {
let r = wraparound_get(input_road_lines, idx);
(r.id, &r.fwd_pl, &r.back_pl)
};
// TODO Ahhh these names are confusing. Adjacent to the fwd_pl, but it's a back pl.
let adj_back_pl = &wraparound_get(input_road_lines, idx + 1).fwd_pl;
let adj_fwd_pl = &wraparound_get(input_road_lines, idx - 1).back_pl;

let r = &results.roads[&id];

// Include collisions between polylines of adjacent roads, so the polygon doesn't cover area
// not originally covered by the thick road bands.
// Always take the second_half here to handle roads that intersect at multiple points.
// TODO Should maybe do reversed() to fwd_pl here too. And why not make all the lines
// passed in point AWAY from the intersection instead?
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 {id} and something, because \
stuff's too short"
);
}

// Shift those final centers out again to find the main endpoints for the polygon.
if r.dst_i == results.intersection_id {
endpoints.push(r.trimmed_center_line.shift_right(r.half_width())?.last_pt());
endpoints.push(r.trimmed_center_line.shift_left(r.half_width())?.last_pt());
} else {
endpoints.push(r.trimmed_center_line.shift_left(r.half_width())?.first_pt());
endpoints.push(
r.trimmed_center_line
.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 {id} and something, because \
stuff's too short"
);
}
}

// There are bad polygons caused by weird short roads. As a temporary workaround, detect cases
// where polygons dramatically double back on themselves and force the polygon to proceed
// around its center.
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);

results.intersection_polygon = if main_result.len() == deduped.len() {
Ring::must_new(main_result).into_polygon()
} else {
warn!(
"{}'s polygon has weird repeats, forcibly removing points",
results.intersection_id
);
Ring::must_new(deduped).into_polygon()
};

// TODO Or always sort points? Helps some cases, hurts other for downtown Seattle.
/*endpoints.sort_by_key(|pt| pt.to_hashable());
endpoints = Pt2D::approx_dedupe(endpoints, Distance::meters(0.1));
let center = Pt2D::center(&endpoints);
endpoints.sort_by_key(|pt| pt.angle_to(center).normalized_degrees() as i64);
close_off_polygon(endpoints)*/

Ok(results)
}

fn pretrimmed_geometry(mut results: Results, road_lines: &[RoadLine]) -> Result<Results> {
let mut endpoints: Vec<Pt2D> = Vec::new();
for r in road_lines {
let r = &results.roads[&r.id];
// Shift those final centers out again to find the main endpoints for the polygon.
if r.dst_i == results.intersection_id {
endpoints.push(r.trimmed_center_line.shift_right(r.half_width())?.last_pt());
endpoints.push(r.trimmed_center_line.shift_left(r.half_width())?.last_pt());
} else {
endpoints.push(r.trimmed_center_line.shift_left(r.half_width())?.first_pt());
endpoints.push(
r.trimmed_center_line
.shift_right(r.half_width())?
.first_pt(),
);
}
}

// TODO Do all of the crazy deduping that calculate_polygon does?
results.intersection_polygon = Ring::new(close_off_polygon(Pt2D::approx_dedupe(
endpoints,
Distance::meters(0.1),
)))?
.into_polygon();
Ok(results)
finalize_intersection_polygon(results, input_road_lines)
}

fn deadend(mut results: Results, road_lines: &[RoadLine]) -> Result<Results> {
Expand Down Expand Up @@ -395,7 +275,6 @@ fn deadend(mut results: Results, road_lines: &[RoadLine]) -> Result<Results> {

// After trimming the center points, the two sides of the road may be at different
// points, so shift the center out again to find the endpoints.
// TODO Refactor with generalized_trim_back.
let mut endpts = vec![pl_b.last_pt(), pl_a.last_pt()];
if r.dst_i == results.intersection_id {
endpts.push(trimmed.shift_right(r.half_width())?.last_pt());
Expand All @@ -404,20 +283,13 @@ fn deadend(mut results: Results, road_lines: &[RoadLine]) -> Result<Results> {
endpts.push(trimmed.shift_left(r.half_width())?.first_pt());
endpts.push(trimmed.shift_right(r.half_width())?.first_pt());
}
endpts.push(endpts[0]);

endpts.dedup();
results.intersection_polygon = Ring::must_new(close_off_polygon(endpts)).into_polygon();
let ring = Ring::deduping_new(endpts)?;
results.intersection_polygon = ring.into_polygon();
Ok(results)
}

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
}

// The lines all end at the intersection
struct Piece {
id: RoadID,
Expand All @@ -430,7 +302,7 @@ struct Piece {
// The normal generalized_trim_back approach produces huge intersections when 3 roads meet at
// certain angles. It usually happens for highway on/off ramps. Try something different here. In
// lieu of proper docs, see https://twitter.com/CarlinoDustin/status/1290799086036111360.
fn on_off_ramp(mut results: Results, road_lines: Vec<RoadLine>) -> Option<Results> {
fn on_off_ramp(mut results: Results, road_lines: Vec<RoadLine>) -> Option<Result<Results>> {
if road_lines.len() != 3 {
return None;
}
Expand All @@ -454,7 +326,7 @@ fn on_off_ramp(mut results: Results, road_lines: Vec<RoadLine>) -> Option<Result

let mut pieces = Vec::new();
// TODO Use this abstraction for all the code here?
for r in road_lines {
for r in &road_lines {
let road = &results.roads[&r.id];
let center = if road.dst_i == results.intersection_id {
road.trimmed_center_line.clone()
Expand All @@ -464,9 +336,9 @@ fn on_off_ramp(mut results: Results, road_lines: Vec<RoadLine>) -> Option<Result
pieces.push(Piece {
id: road.id,
dst_i: road.dst_i,
left: r.back_pl,
left: r.back_pl.clone(),
center,
right: r.fwd_pl,
right: r.fwd_pl.clone(),
});
}

Expand Down Expand Up @@ -589,48 +461,26 @@ fn on_off_ramp(mut results: Results, road_lines: Vec<RoadLine>) -> Option<Result
}
}

// Now build the actual polygon
let mut endpoints = Vec::new();
for id in [thin.id, thick1.id, thick2.id] {
let r = &results.roads[&id];
// Shift those final centers out again to find the main endpoints for the polygon.
if r.dst_i == results.intersection_id {
endpoints.push(
r.trimmed_center_line
.shift_right(r.half_width())
.ok()?
.last_pt(),
);
endpoints.push(
r.trimmed_center_line
.shift_left(r.half_width())
.ok()?
.last_pt(),
);
} else {
endpoints.push(
r.trimmed_center_line
.shift_left(r.half_width())
.ok()?
.first_pt(),
);
endpoints.push(
r.trimmed_center_line
.shift_right(r.half_width())
.ok()?
.first_pt(),
);
}
Some(finalize_intersection_polygon(results, &road_lines))
}

fn finalize_intersection_polygon(
mut results: Results,
input_road_lines: &[RoadLine],
) -> Result<Results> {
let mut sorted_roads = Vec::new();
// Just use RoadLines to remember the sorting order
for line in input_road_lines {
sorted_roads.push(&results.roads[&line.id]);
}

let mut endpts = Vec::new();
// TODO Look at pairs. If the points already match in a corner, ignore, but otherwise calculate the intersection again
for edge in RoadEdge::calculate(sorted_roads, results.intersection_id) {
endpts.push(edge.pl.last_pt());
}
/*for (idx, pt) in endpoints.iter().enumerate() {
debug.push((format!("{}", idx), Circle::new(*pt, Distance::meters(2.0)).to_polygon()));
}*/

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();
results.intersection_polygon = Ring::must_new(close_off_polygon(endpoints)).into_polygon();
Some(results)
endpts.push(endpts[0]);
let ring = Ring::deduping_new(endpts)?;
results.intersection_polygon = ring.into_polygon();
Ok(results)
}
51 changes: 8 additions & 43 deletions osm2streets/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ use std::path::Path;
use anyhow::Result;
use geom::{ArrowCap, Distance, Line, PolyLine, Polygon, Ring};

use crate::{
DebugStreets, Direction, DrivingSide, Intersection, LaneSpec, LaneType, RoadID, StreetNetwork,
};
use crate::road::RoadEdge;
use crate::{DebugStreets, Direction, DrivingSide, Intersection, LaneType, StreetNetwork};

impl StreetNetwork {
/// Saves the plain GeoJSON rendering to a file.
Expand Down Expand Up @@ -398,47 +397,13 @@ fn make_props(list: &[(&str, serde_json::Value)]) -> serde_json::Map<String, ser
// TODO Where should this live?
/// For an intersection, show all corners where sidewalks meet.
fn make_sidewalk_corners(streets: &StreetNetwork, intersection: &Intersection) -> Vec<Polygon> {
#[derive(Clone)]
struct Edge {
road: RoadID,
// Pointed into the intersection
pl: PolyLine,
lane: LaneSpec,
}

// Get the left and right edge of each road, pointed into the intersection. All sorted
// clockwise
// TODO Use the road view idea instead. Or just refactor this.
let mut edges = Vec::new();
for road in streets.roads_per_intersection(intersection.id) {
let mut left = Edge {
road: road.id,
pl: road
.trimmed_center_line
.must_shift_left(road.total_width() / 2.0),
lane: road.lane_specs_ltr[0].clone(),
};
let mut right = Edge {
road: road.id,
pl: road
.trimmed_center_line
.must_shift_right(road.total_width() / 2.0),
lane: road.lane_specs_ltr.last().unwrap().clone(),
};
if road.dst_i == intersection.id {
edges.push(right);
edges.push(left);
} else {
left.pl = left.pl.reversed();
right.pl = right.pl.reversed();
edges.push(left);
edges.push(right);
}
}

// Look at every adjacent pair
let mut results = Vec::new();
// Look at every adjacent pair of edges
let mut edges = RoadEdge::calculate(
streets.roads_per_intersection(intersection.id),
intersection.id,
);
edges.push(edges[0].clone());
let mut results = Vec::new();
for pair in edges.windows(2) {
let one = &pair[0];
let two = &pair[1];
Expand Down
45 changes: 45 additions & 0 deletions osm2streets/src/road.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,48 @@ impl StreetNetwork {
id
}
}

/// The edge of a road, pointed into some intersection
#[derive(Clone)]
pub(crate) struct RoadEdge {
pub road: RoadID,
/// Pointed into the intersection
pub pl: PolyLine,
pub lane: LaneSpec,
}

impl RoadEdge {
/// Get the left and right edge of each road, pointed into the intersection. All sorted
/// clockwise. No repetitions -- to iterate over all adjacent pairs, the caller must repeat the
/// first edge
// TODO Maybe returning an iterator over pairs of these is more useful
pub fn calculate(sorted_roads: Vec<&Road>, i: IntersectionID) -> Vec<Self> {
let mut edges = Vec::new();
for road in sorted_roads {
let mut left = RoadEdge {
road: road.id,
pl: road
.trimmed_center_line
.must_shift_left(road.total_width() / 2.0),
lane: road.lane_specs_ltr[0].clone(),
};
let mut right = RoadEdge {
road: road.id,
pl: road
.trimmed_center_line
.must_shift_right(road.total_width() / 2.0),
lane: road.lane_specs_ltr.last().unwrap().clone(),
};
if road.dst_i == i {
edges.push(right);
edges.push(left);
} else {
left.pl = left.pl.reversed();
right.pl = right.pl.reversed();
edges.push(left);
edges.push(right);
}
}
edges
}
}

0 comments on commit 682c6ff

Please sign in to comment.