From da8a221702644b72ff228a840d14f01c9d49bcc8 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 7 Dec 2022 13:05:24 +0000 Subject: [PATCH] Special-case geometry for degenerate intersections. #136 --- osm2streets/src/geometry/algorithm.rs | 3 ++ osm2streets/src/geometry/degenerate.rs | 53 +++++++++++++++++++++++++ osm2streets/src/geometry/mod.rs | 10 +++++ osm2streets/src/geometry/on_off_ramp.rs | 7 +--- osm2streets/src/geometry/terminus.rs | 8 +--- osm2streets/src/intersection.rs | 2 + 6 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 osm2streets/src/geometry/degenerate.rs diff --git a/osm2streets/src/geometry/algorithm.rs b/osm2streets/src/geometry/algorithm.rs index cbf7a6d8..e0554855 100644 --- a/osm2streets/src/geometry/algorithm.rs +++ b/osm2streets/src/geometry/algorithm.rs @@ -81,6 +81,9 @@ pub fn intersection_polygon( if road_lines.len() == 1 { return super::terminus::terminus(results, roads.into_values().next().unwrap()); + } else if road_lines.len() == 2 { + let mut iter = roads.into_values(); + return super::degenerate::degenerate(results, iter.next().unwrap(), iter.next().unwrap()); } if !trim_roads_for_merging.is_empty() { diff --git a/osm2streets/src/geometry/degenerate.rs b/osm2streets/src/geometry/degenerate.rs new file mode 100644 index 00000000..80ef57d2 --- /dev/null +++ b/osm2streets/src/geometry/degenerate.rs @@ -0,0 +1,53 @@ +use anyhow::Result; +use geom::{Distance, Ring}; + +use super::Results; +use crate::InputRoad; + +/// For intersections between exactly 2 roads, just trim back a bit. +pub(crate) fn degenerate( + mut results: Results, + road1: InputRoad, + road2: InputRoad, +) -> Result { + // Make the intersection shape roughly square, relative to the thinner road + let intersection_half_len = Distance::meters(1.0); + let min_road_len = 2.0 * intersection_half_len; + + // Make both roads point at the intersection, to simplify logic below + let mut center1 = road1.center_line_pointed_at(results.intersection_id); + let mut center2 = road2.center_line_pointed_at(results.intersection_id); + + // If either road is too short, just fail outright. What else should we do? + // TODO Also, if we haven't trimmed the other side yet, we don't have the full picture + if center1.length() < min_road_len || center2.length() < min_road_len { + bail!("Road is too short to trim for a degenerate intersection"); + } + + // Trim each. + center1 = center1.exact_slice(Distance::ZERO, center1.length() - intersection_half_len); + center2 = center2.exact_slice(Distance::ZERO, center2.length() - intersection_half_len); + + // Make the square polygon + let mut endpts = vec![ + center1.shift_left(road1.half_width())?.last_pt(), + center2.shift_right(road2.half_width())?.last_pt(), + center2.shift_left(road2.half_width())?.last_pt(), + center1.shift_right(road1.half_width())?.last_pt(), + ]; + endpts.push(endpts[0]); + + results.intersection_polygon = Ring::deduping_new(endpts)?.into_polygon(); + + // Fix orientation if needed + if road1.src_i == results.intersection_id { + center1 = center1.reversed(); + } + if road2.src_i == results.intersection_id { + center2 = center2.reversed(); + } + + results.trimmed_center_pts.insert(road1.id, center1); + results.trimmed_center_pts.insert(road2.id, center2); + Ok(results) +} diff --git a/osm2streets/src/geometry/mod.rs b/osm2streets/src/geometry/mod.rs index 49058c1f..f3900529 100644 --- a/osm2streets/src/geometry/mod.rs +++ b/osm2streets/src/geometry/mod.rs @@ -9,6 +9,7 @@ //! I wrote a novella about this: mod algorithm; +mod degenerate; mod on_off_ramp; mod terminus; @@ -38,6 +39,15 @@ impl InputRoad { pub fn half_width(&self) -> Distance { self.total_width / 2.0 } + + pub fn center_line_pointed_at(&self, i: IntersectionID) -> PolyLine { + if self.dst_i == i { + self.center_line.clone() + } else { + assert_eq!(self.src_i, i); + self.center_line.reversed() + } + } } #[derive(Clone)] diff --git a/osm2streets/src/geometry/on_off_ramp.rs b/osm2streets/src/geometry/on_off_ramp.rs index 3443c59c..ed535dcd 100644 --- a/osm2streets/src/geometry/on_off_ramp.rs +++ b/osm2streets/src/geometry/on_off_ramp.rs @@ -39,16 +39,11 @@ pub(crate) fn on_off_ramp( // TODO Use this abstraction for all the code here? for r in road_lines { let road = &roads[&r.id]; - let center = if road.dst_i == results.intersection_id { - road.center_line.clone() - } else { - road.center_line.reversed() - }; pieces.push(Piece { id: road.id, dst_i: road.dst_i, left: r.back_pl, - center, + center: road.center_line_pointed_at(results.intersection_id), right: r.fwd_pl, }); } diff --git a/osm2streets/src/geometry/terminus.rs b/osm2streets/src/geometry/terminus.rs index 4c77d48b..491681f6 100644 --- a/osm2streets/src/geometry/terminus.rs +++ b/osm2streets/src/geometry/terminus.rs @@ -6,12 +6,8 @@ use crate::InputRoad; /// For dead-ends and map edges, just use a piece of the road as the intersection. pub(crate) fn terminus(mut results: Results, road: InputRoad) -> Result { - // Point at the intersection so we can be simple below - let mut center = if road.dst_i == results.intersection_id { - road.center_line.clone() - } else { - road.center_line.reversed() - }; + // Point at the intersection, to simplify logic below + let mut center = road.center_line_pointed_at(results.intersection_id); // Make the intersection roughly square let intersection_len = road.total_width; diff --git a/osm2streets/src/intersection.rs b/osm2streets/src/intersection.rs index 855ac612..d9084cf3 100644 --- a/osm2streets/src/intersection.rs +++ b/osm2streets/src/intersection.rs @@ -18,6 +18,8 @@ pub struct Intersection { /// StreetNetwork; roads and intersections get merged and deleted. pub point: Pt2D, /// This will be a placeholder until `Transformation::GenerateIntersectionGeometry` runs. + /// + /// TODO Consistently make this clockwise. pub polygon: Polygon, pub kind: IntersectionKind, pub control: IntersectionControl,