-
Notifications
You must be signed in to change notification settings - Fork 10
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
Add turn movements #207
base: main
Are you sure you want to change the base?
Add turn movements #207
Conversation
Tests simply rename "movements" list to "turns"
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.
Really cool to see this!
Not that I'm likely to play with the JOSM side soon, but is https://github.com/BudgieInWA/JOSM2Streets/ still up-to-date, or are there new rendering goodies corresponding to this?
|
||
match intersection.kind { | ||
IntersectionKind::Connection => { | ||
for turn in intersection.turns.iter_mut() { |
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.
for turn in intersection.turns.iter_mut() { | |
for turn in &mut intersection.turns { |
Optional, not really sure what style is preferable. If we later need to do anything else with the iterator, like .enumerate()
, then how you have it is nicer (because we'd switch back to it)
|
||
/// A specific path that a vehicle can take through a turn. | ||
/// | ||
/// Lane numbers are counted from the perspective of the direction of the turn. |
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.
This stumped me for a few minutes; can we add an example or clarify the comment? LaneIDs normally index into lanes_ltr
. Normally whatever direction we're thinking about a road, the lane ID is "absolute" -- 0 means the left side of the road based on its OSM-defined orientation. It feels like here we have a sudden explosion of possibilities, if we point from the right side of one road to the left side of another in the backwards direction, for instance...
And actually now I'm wondering if this refers to LtrLaneNum
and not LaneID
and am more confused :\
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.
D'oh, this is an outdated comment. You're right, it was referring to LtrLaneNum
, trying to clarify about Forward
/Backward
variants.
} | ||
|
||
/// Limitations: ignores both ways lanes | ||
pub fn default( |
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.
Getting tests of some form here is important IMHO. We could maybe make unit tests if we can succinctly express input and output, or maybe it's better to do our usual snapshot+diff testing approach, and get more rendering into Street Explorer
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 unit tests is a great idea here, and covering at least some of these code paths in our snapshot tests is important too.
I keep finding myself wanting to create "unit test" like snapshot tests (with very simple, contrived inputs), instead of the large "integration tests" that our current snapshot tests are. Maybe that will be worthwhile at some point, but unit testing these kinds of functions is an easier way to to address my want!
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.
Hmm... nothing would stop us from drawing something synthetic in JOSM and adding the .osm to tests/
like we normally do
// Lanes through intersections | ||
for inter in self.intersections.values() { | ||
for Turn { movements, .. } in &inter.turns { | ||
let Some(movements) = movements else { continue }; |
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.
OK I actually hadn't understood the point of let-else till this; very cool!
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.
Yep, a bunch of nesting can be replaced with early returns! The else
block can run any code, as long as it diverges (doesn't run to the end).
@@ -304,6 +335,10 @@ impl StreetNetwork { | |||
|
|||
areas | |||
} | |||
|
|||
fn lane(&self, lane: LaneID) -> Option<&LaneSpec> { |
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.
Could we move to road.rs
in the impl StreetNetwork
block?
pairs.push((polygon.to_geojson(Some(&self.gps_bounds)), make_props(&[]))); | ||
} | ||
} | ||
if pairs.is_empty() { |
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 general, when should we expect turns to exist, but not movements? Right now update_i
does both. Is this temporary before we have movements defined in all cases?
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.
Yes, that was a temporary measure. I don't know where it will land though. I think we will be able to calculate movements any time we calculate turns, so we will be able to remove the Option
.
I was playing with using an Option
in places where data is only present after some step of the algorithm, instead of using *::dummy()
kind of values.
@@ -467,6 +515,23 @@ impl Road { | |||
Ok((left, right)) | |||
} | |||
|
|||
pub fn intersection_at(&self, end: RoadEnd) -> IntersectionID { |
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.
These new helpers are nice; there's probably many places throughout the code where they can come in handy!
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.
Using RoadEnd
has been indispensable while dealing with the possibility space explosion in crate::movements
, and these helpers remove a lot of noise from those implementation sites that make it possible to see the (still very complicated) logic there.
|
||
/// A specific path that a vehicle can take through a turn. | ||
/// | ||
/// Lane numbers are counted from the perspective of the direction of the turn. |
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.
D'oh, this is an outdated comment. You're right, it was referring to LtrLaneNum
, trying to clarify about Forward
/Backward
variants.
} | ||
|
||
/// Limitations: ignores both ways lanes | ||
pub fn default( |
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 unit tests is a great idea here, and covering at least some of these code paths in our snapshot tests is important too.
I keep finding myself wanting to create "unit test" like snapshot tests (with very simple, contrived inputs), instead of the large "integration tests" that our current snapshot tests are. Maybe that will be worthwhile at some point, but unit testing these kinds of functions is an easier way to to address my want!
// Lanes through intersections | ||
for inter in self.intersections.values() { | ||
for Turn { movements, .. } in &inter.turns { | ||
let Some(movements) = movements else { continue }; |
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.
Yep, a bunch of nesting can be replaced with early returns! The else
block can run any code, as long as it diverges (doesn't run to the end).
"movements", | ||
"turns", | ||
serde_json::Value::Array( | ||
intersection | ||
.movements | ||
.turns | ||
.iter() | ||
.map(|(a, b)| format!("{a} -> {b}").into()) | ||
.map(|Turn { from, to, .. }| format!("{from} -> {to}").into()) |
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 need to add TurnMovements
to the test output to observe these changes.
pairs.push((polygon.to_geojson(Some(&self.gps_bounds)), make_props(&[]))); | ||
} | ||
} | ||
if pairs.is_empty() { |
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.
Yes, that was a temporary measure. I don't know where it will land though. I think we will be able to calculate movements any time we calculate turns, so we will be able to remove the Option
.
I was playing with using an Option
in places where data is only present after some step of the algorithm, instead of using *::dummy()
kind of values.
@@ -467,6 +515,23 @@ impl Road { | |||
Ok((left, right)) | |||
} | |||
|
|||
pub fn intersection_at(&self, end: RoadEnd) -> IntersectionID { |
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.
Using RoadEnd
has been indispensable while dealing with the possibility space explosion in crate::movements
, and these helpers remove a lot of noise from those implementation sites that make it possible to see the (still very complicated) logic there.
path: PolyLine::new(vec![src_pt, dst_pt]) | ||
.unwrap_or_else(|_| PolyLine::must_new(vec![src_pt, Pt2D::new(0.0, 0.0), dst_pt])), |
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.
This is where I would want to create a nice curved path, instead of just jamming two points in.
After working with it, it might be better to remove the geometry aspect from here and calculate it later, or on the fly. That matches up better with where geometry of other kinds is calculated.
Also, the or_else
polyline sometimes crashes when the first one does, but not always!
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.
On-the-fly geom could be nice, yeah, and would have reduce file size requirements maybe. (But it's not a clear-cut tradeoff; lazily calculating in a UI later can be sluggish.)
Also, the or_else polyline sometimes crashes when the first one does, but not always!
I actually didn't look at this carefully; inserting (0, 0) in the middle is an attempt to make the total line length larger? In what kind of cases is the above PolyLine::new(vec![src_pt, dst_pt])
failing -- when the two points are very close together? What should we do in those cases -- maybe it's valid to just have no movement geometry?
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.
Yes, when the points are close together. Given our current approach, we shouldn't have any zero length movements, but I think it happens coincidently sometimes.
I'm going to experiment with removing path
from TurnMovement
so that turns and movement are only concerned with connectivity for now (and don't have to be recalculated if trim distances change, or something like that). I want to think about storing lane centerlines and movement paths in the same way.
The JOSM2Streets build embeds |
Hi, is this still being considered? |
I don't think Ben or I have had time to work on this, so not anytime soon. Are you interested in picking it up? |
We don't need all of this before merging, but here's the plan so far:
Movement
->Turn
(road level), addTurnMovement
(lane level)TurnMovement
s for degenerate (2 road) intersectionsIntersectionKind::Connection
s with more than two roads.TurnKind
to determine what kind of lane markings theTurnMovement
s get.This is making some good progress towards lane level movements. In fact, some movements are calculated and drawn! I added the movement centrelines in post, the left edge of the movements are being drawn:
We need angled road ends, #95, to draw these markings properly.