-
-
Notifications
You must be signed in to change notification settings - Fork 353
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
Make the conversion from geo::Polygon to geom::Polygon fallible #980
Conversation
ValidationOh yeah, and this PR has some impacts on bits of downstream code. I'll comment inline in the changed code. I will need to reimport all maps, where I might hit a few new problems, but I think I understand all the effect right now |
); | ||
GeomBatch::from(vec![(app.cs.fade_map_dark, fade_area)]) | ||
} | ||
Err(_) => { |
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.
@@ -509,12 +509,14 @@ fn cluster_jams(map: &Map, problems: Vec<(IntersectionID, Time)>) -> Vec<(Polygo | |||
} | |||
} | |||
|
|||
// TODO This silently hides jams where we can't calculate the convex hull. The caller just |
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.
Here's an annoying consequence of strict checks and fallible conversions
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.
Would it be feasible to have a convex_hull method that returns a tesselation?
It seems like what would be failing is the abstreet::Polygon <-> geo::Polygon
conversion, right?
Maybe being able to tesselate from a geo::Polygon -> abstreet::Tesselation
rather than geo::Polygon -> abstreet::Polygon -> abstreet::Tesselation
would help mitigate the issue.
related? feature request: georust/geo#568
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.
Looking at the 3 callers of convex_hull
, they just need to tessellate, not do anything with a full polygon. I'm going to just change the API to return Tessellation
or make a second call...
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.
Actually I'm wrong, all 3 need a Polygon (well, without more work to make some operations handle either Polygon or Tessellation). Keeping this for now.
I wrote a impl From<geo::Polygon> for Tessellation
. I'll go ahead and commit it, even though it's unused right now.
@@ -181,14 +181,17 @@ fn clip_map(map: &mut RawMap, timer: &mut Timer) { | |||
|
|||
let mut result_areas = Vec::new(); | |||
for orig_area in map.areas.drain(..) { | |||
for polygon in map | |||
// If clipping fails, giving up on some areas is fine |
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.
A real example in Milwaukee:
The red polygon disappears with this PR. For some reason, clipping https://www.openstreetmap.org/relation/6041452#map=17/43.05795/-87.87511 to the map's boundary produces an invalid ring somewhere. But as you can see from the "before" screenshot above, the final result used to work fine.
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.
Yeah, it'd be great if One Day™ we added some validation rules, and maybe some heuristics for "guessing" at how to best make an existing invalid geometry valid. e.g. something like https://github.com/libgeos/geos/blob/main/src/operation/valid/MakeValid.cpp
FYI there's a related long standing issue open about implementing just the validity checks: georust/geo#123 but it hasn't seen activity in a long time.
@@ -587,8 +568,12 @@ impl Polygon { | |||
Ok(results) | |||
} | |||
|
|||
/// If simplification fails, just keep the original polygon |
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 an example where all the callers of this would end up doing this kind of thing anyway
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.
The changes seem reasonable, but the regressions are unfortunate. I leave it to you wether you want to address them before merging or Move Fast And Break Things. 😉
@@ -181,14 +181,17 @@ fn clip_map(map: &mut RawMap, timer: &mut Timer) { | |||
|
|||
let mut result_areas = Vec::new(); | |||
for orig_area in map.areas.drain(..) { | |||
for polygon in map | |||
// If clipping fails, giving up on some areas is fine |
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.
Yeah, it'd be great if One Day™ we added some validation rules, and maybe some heuristics for "guessing" at how to best make an existing invalid geometry valid. e.g. something like https://github.com/libgeos/geos/blob/main/src/operation/valid/MakeValid.cpp
FYI there's a related long standing issue open about implementing just the validity checks: georust/geo#123 but it hasn't seen activity in a long time.
@@ -509,12 +509,14 @@ fn cluster_jams(map: &Map, problems: Vec<(IntersectionID, Time)>) -> Vec<(Polygo | |||
} | |||
} | |||
|
|||
// TODO This silently hides jams where we can't calculate the convex hull. The caller just |
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.
Would it be feasible to have a convex_hull method that returns a tesselation?
It seems like what would be failing is the abstreet::Polygon <-> geo::Polygon
conversion, right?
Maybe being able to tesselate from a geo::Polygon -> abstreet::Tesselation
rather than geo::Polygon -> abstreet::Polygon -> abstreet::Tesselation
would help mitigate the issue.
related? feature request: georust/geo#568
// If clipping fails, just use the original polygon. | ||
if let Ok(list) = p.intersection(&result.boundary_polygon) { | ||
clipped.extend(list); | ||
} else { |
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.
It's definitely up to your discretion, but do you want to log these kinds of failures?
I can imagine it being tricky to track down strange geometry bugs if they're now being silently ignored, especially if they were previously crashing. If you're worried about log spam, even just a debug!
(or trace!
) message which normal users wouldn't see might be helpful if you're actively trying to understand strange behavior.
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, maybe. In this case, there will be a very loud visual bug anyway. Logs could be helpful if I ask people to include them in bug reports, but in general, it's easier to just reproduce what they're seeing
has stricter rules for Rings. Finally remove Polygon::buggy_new, and instead make many Polygon methods return a Result. #951 Just internal geom changes here, no changes downstream yet.
4e38a7f
to
964749d
Compare
skipped. This also fixes the bus route names recorded per road.
…pass strict Ring checks. Unused right now. #951
The regressions are in code complexity, not observable behavior -- I haven't found a single example where convex hull fails to return something valid, for example. I'm still in a mild existential crisis about whether or not |
Towards the quest of making
geom::Polygon
always internally just wrap ageo::Polygon
.Why is this necessary?
geom
tries to be stricter thangeo
, rejecting "bad" geometry upfront to try to isolate a problem at its source. ARing
must not have any duplicate points (adjacent or not), except for the first = last point. This is further complicated by "duplicate" points being defined a bit strictly. EveryPt2D
trims its f64's down to a few decimal places.I'm starting to question if this attempt at upfront strictness is even helpful. In the beginning, it was an attempt to "clean up" a bunch of crazy OSM geometry I was encountering or algorithmically producing, but I've lost context on many of the original motivating problems. Many might've been solved in another way by now. Also, the strictness isn't even complete -- my mental model of a
Ring
is that there are no self-intersections at all, but nothing looks for this right now.The strictness is actually only useful in
geom::PolyLine
code anyway. Most of the hand-written geometry logic is there now. Operations like finding a point some distance along the polyline, or projecting a polyline left or right for buffering are easier to reason about without duplicate adjacent points, or even without internal points that're "redundant" from an angle perspective (aka a polyline[(0, 0), (10, 0), (20, 0)]
has a redundant middle point, it could just be[(0, 0), (20, 0)]
).There've been countless times somebody sends me a bug report with an unhelpful error about duplicate points in a ring. Even when the stack trace shows something, it's not easy to reconstruct context about which object in the map caused the problem. My fear has been that if invalid geometry "sneaks in", then there will be rendering, hitbox testing, etc bugs downstream that're hard to catch. But in reality, I can't really say all the strict checks have caught that many problems.
Alternative
I feel like I just convinced myself that the validity checks should actually go away now. The heftier bits of logic in
PolyLine
can just be made to deal with duplicate adjacent points and the like. Any opinions?Next steps
Back to the original goal of making
geom::Polygon
less weird. After this PR (or rethinking the validity checks), I think it'll be:geom::Polygon
transforms sometimes destroying the internal rings through operations like scaling. For this reason,strip_rings
is often used. But then, if the validity checks go away, maybe this isn't actually a problem.Circle::to_polygon
andPolyLine::make_polygons
should return. They pre-tessellate right now, which is useful for performance. But many callers also need the realPolygon
too, to produce outlines or do other operations.geom::Polygon
representation to either wrapgeo::Polygon
or just beexterior: Ring, holes: Vec<Ring>