-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cache roads per intersection. #93 #98
Changes from all commits
ef623fb
e91e94b
f4b5f67
9cb1679
b16ca54
b888748
84db6c4
e6de03c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,28 +159,45 @@ impl StreetNetwork { | |
} | ||
} | ||
|
||
// TODO Might be better to maintain this instead of doing a search everytime. | ||
pub fn roads_per_intersection(&self, i: osm::NodeID) -> Vec<OriginalRoad> { | ||
let mut results = Vec::new(); | ||
for id in self.roads.keys() { | ||
if id.i1 == i || id.i2 == i { | ||
results.push(*id); | ||
} | ||
pub fn insert_road(&mut self, id: OriginalRoad, road: Road) { | ||
self.roads.insert(id, road); | ||
for i in [id.i1, id.i2] { | ||
self.intersections.get_mut(&i).unwrap().roads.push(id); | ||
self.sort_roads(i); | ||
} | ||
results | ||
} | ||
|
||
pub fn new_osm_node_id(&self, start: i64) -> osm::NodeID { | ||
assert!(start < 0); | ||
// Slow, but deterministic. | ||
let mut osm_node_id = start; | ||
loop { | ||
if self.intersections.keys().any(|i| i.0 == osm_node_id) { | ||
osm_node_id -= 1; | ||
} else { | ||
return osm::NodeID(osm_node_id); | ||
pub fn remove_road(&mut self, id: &OriginalRoad) -> Road { | ||
// Since the roads are already sorted, removing doesn't hurt anything | ||
self.intersections | ||
.get_mut(&id.i1) | ||
.unwrap() | ||
.roads | ||
.retain(|r| r != id); | ||
self.intersections | ||
.get_mut(&id.i2) | ||
.unwrap() | ||
.roads | ||
.retain(|r| r != id); | ||
self.roads.remove(id).unwrap() | ||
} | ||
|
||
pub fn retain_roads<F: Fn(&OriginalRoad, &Road) -> bool>(&mut self, f: F) { | ||
let mut remove = Vec::new(); | ||
for (id, r) in &self.roads { | ||
if !f(id, r) { | ||
remove.push(*id); | ||
} | ||
} | ||
for id in remove { | ||
self.remove_road(&id); | ||
} | ||
} | ||
|
||
// This always returns roads oriented in clockwise order around the intersection | ||
// TODO Consider not cloning. Many callers will have to change | ||
pub fn roads_per_intersection(&self, i: osm::NodeID) -> Vec<OriginalRoad> { | ||
self.intersections[&i].roads.clone() | ||
} | ||
|
||
/// (Intersection polygon, polygons for roads, list of labeled polygons to debug) | ||
|
@@ -286,6 +303,60 @@ impl StreetNetwork { | |
.push((self.roads[&r].untrimmed_road_geometry().0, label.into())); | ||
} | ||
} | ||
|
||
// Restore the invariant that an intersection's roads are ordered clockwise | ||
// | ||
// TODO This doesn't handle trim_roads_for_merging | ||
fn sort_roads(&mut self, i: osm::NodeID) { | ||
let intersection = self.intersections.get_mut(&i).unwrap(); | ||
|
||
// (ID, polyline pointing to the intersection, sorting point that's filled out later) | ||
let mut road_centers = Vec::new(); | ||
let mut endpoints_for_center = Vec::new(); | ||
for r in &intersection.roads { | ||
let road = &self.roads[r]; | ||
// road.center_pts is unadjusted; it doesn't handle unequal widths yet. But that | ||
// shouldn't matter for sorting. | ||
let center_pl = if r.i1 == i { | ||
PolyLine::must_new(road.osm_center_points.clone()).reversed() | ||
} else if r.i2 == i { | ||
PolyLine::must_new(road.osm_center_points.clone()) | ||
} else { | ||
panic!("Incident road {r} doesn't have an endpoint at {i}"); | ||
}; | ||
endpoints_for_center.push(center_pl.last_pt()); | ||
|
||
road_centers.push((*r, center_pl, Pt2D::zero())); | ||
} | ||
// In most cases, this will just be the same point repeated a few times, so Pt2D::center is a | ||
// no-op. But when we have pretrimmed roads, this is much closer to the real "center" of the | ||
// polygon we're attempting to create. | ||
let intersection_center = Pt2D::center(&endpoints_for_center); | ||
|
||
// Sort the road polylines in clockwise order around the center. This is subtle -- | ||
// https://a-b-street.github.io/docs/tech/map/geometry/index.html#sorting-revisited. When we | ||
// get this wrong, the resulting polygon looks like a "bowtie," because the order of the | ||
// intersection polygon's points follows this clockwise ordering of roads. | ||
// | ||
// We could use the point on each road center line farthest from the intersection center. But | ||
// when some of the roads bend around, this produces incorrect ordering. Try walking along that | ||
// center line a distance equal to the _shortest_ road. | ||
let shortest_center = road_centers | ||
.iter() | ||
.map(|(_, pl, _)| pl.length()) | ||
.min() | ||
.unwrap(); | ||
for (_, pl, sorting_pt) in &mut road_centers { | ||
*sorting_pt = pl.must_dist_along(pl.length() - shortest_center).0; | ||
} | ||
road_centers.sort_by_key(|(_, _, sorting_pt)| { | ||
sorting_pt | ||
.angle_to(intersection_center) | ||
.normalized_degrees() as i64 | ||
}); | ||
|
||
intersection.roads = road_centers.into_iter().map(|(r, _, _)| r).collect(); | ||
} | ||
} | ||
|
||
// Mutations and supporting queries | ||
|
@@ -544,6 +615,10 @@ pub struct Intersection { | |
pub control: ControlType, | ||
pub elevation: Distance, | ||
|
||
/// All roads connected to this intersection. They may be incoming or outgoing relative to this | ||
/// intersection. They're ordered clockwise aroundd the intersection. | ||
pub roads: Vec<OriginalRoad>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a good type that has an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like https://doc.rust-lang.org/std/vec/struct.Vec.html#method.insert ? When we're adding a new road, we could calculate its position in the list and add it, or just recalculate the ordering for the intersection. It'll depend on the sorting algorithm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. Performance doesn't really matter, so |
||
|
||
// true if src_i matches this intersection (or the deleted/consolidated one, whatever) | ||
pub trim_roads_for_merging: BTreeMap<(osm::WayID, bool), Pt2D>, | ||
} | ||
|
@@ -555,6 +630,7 @@ impl Intersection { | |
complexity, | ||
control, | ||
// Filled out later | ||
roads: Vec::new(), | ||
elevation: Distance::ZERO, | ||
trim_roads_for_merging: BTreeMap::new(), | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -138,6 +138,7 @@ pub fn collapse_intersection(streets: &mut StreetNetwork, i: NodeID) { | |
assert_eq!(roads.len(), 2); | ||
let mut r1 = roads[0]; | ||
let mut r2 = roads[1]; | ||
assert_ne!(r1, r2); | ||
|
||
// We'll keep r1's way ID, so it's a little more convenient for debugging to guarantee r1 is | ||
// the longer piece. | ||
|
@@ -157,11 +158,11 @@ pub fn collapse_intersection(streets: &mut StreetNetwork, i: NodeID) { | |
} | ||
|
||
info!("Collapsing degenerate {}", i); | ||
streets.intersections.remove(&i).unwrap(); | ||
// We could be more careful merging percent_incline and osm_tags, but in practice, it doesn't | ||
// matter for the short segments we're merging. | ||
let mut new_road = streets.roads.remove(&r1).unwrap(); | ||
let mut road2 = streets.roads.remove(&r2).unwrap(); | ||
let mut new_road = streets.remove_road(&r1); | ||
let mut road2 = streets.remove_road(&r2); | ||
streets.intersections.remove(&i).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Order matters! |
||
|
||
// There are 4 cases, easy to understand on paper. Preserve the original direction of r1 | ||
let (new_i1, new_i2) = if r1.i2 == r2.i1 { | ||
|
@@ -195,7 +196,7 @@ pub fn collapse_intersection(streets: &mut StreetNetwork, i: NodeID) { | |
i1: new_i1, | ||
i2: new_i2, | ||
}; | ||
streets.roads.insert(new_r1, new_road); | ||
streets.insert_road(new_r1, new_road); | ||
|
||
// We may need to fix up turn restrictions. r1 and r2 both become new_r1. | ||
let rewrite = |x: &OriginalRoad| *x == r1 || *x == r2; | ||
|
@@ -242,7 +243,7 @@ pub fn trim_deadends(streets: &mut StreetNetwork) { | |
} | ||
|
||
for r in remove_roads { | ||
streets.roads.remove(&r).unwrap(); | ||
streets.remove_road(&r); | ||
} | ||
for i in remove_intersections { | ||
streets.delete_intersection(i); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In cases where we remove a short road and merge intersections, sorting around a "center" point gets kind of funny.
geometry/algorithm.rs
takes care to try and handle that. I didn't port the same logic here yet.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best way to determine the order of roads in those multi-node intersections from scratch is to use the OSM graph structure. First sorting the nodes (that are the Road endpoints) then by bearing of the first OSM segment. #98 (comment)
This would require keeping the OSM graph around in some capacity, at least within multi-node intersections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with keeping the graph structure around somehow. Preserving turn restrictions when we merge gets very complex, and a possibly simpler approach is to use the original graph and just ask questions about it. Keeping the graph also means we can map back to objects in OSM more directly.
I don't know how this would work, so shelving the idea for the moment, but you've definitely got me thinking...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like "Asking questions about it". In fact, load and leave the osm graph completely in place. Every road should get it's own copy of the geom (like when you merge similar ways currently) which can be promptly offset to account for placement. Then further tweaked if needed (like sausage links). (Think of an editor, where the graph is literally drawn on the map. One could point at a lane to edit the way.)
(Roads and intersections need to have IDs independant of the OSM ids they reference, [and they need to know their own ID, so we don't need to pass pairs eveywhere])
Asking questions about roads is how I'm thinking about movements too. It's like I want to iterate flows of traffic clockwise around the intersection. A way could yield a single (
oneway
) flow, or commonly two. An intersection provides aniter_cw_flows() -> Flow
, where Flow is a view onto a subset of lanes on a Road (that all go in the same direction).I think that's how the type system helps me out here: There exists a whole lot of data about a whole lot of lanes, laid out in memory in one way or another, I want to slice it up differently, with differently typed structs, for different tasks. It's like the HashMap Entry type.
Keeping an immutable copy of the OSM graph and making the Street graph independent will no doubt become GH Issues to discuss and target soon.