Skip to content
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

Should geom::Polygon.points be closed or not? #2

Open
michaelkirk opened this issue Jun 30, 2022 · 12 comments
Open

Should geom::Polygon.points be closed or not? #2

michaelkirk opened this issue Jun 30, 2022 · 12 comments

Comments

@michaelkirk
Copy link

michaelkirk commented Jun 30, 2022

I wanted to test out some new georust/geo features in the context of abstreet, and consequently I've been spelunking in abstreet::geom.

I ran into some inconsistencies while trying to roundtrip some abstreet::geom::Polygyon to/from geo::Polygon - it resulted in some corrupt rendering like this:

Screen Shot 2022-06-29 at 5 35 10 PM

My unverified hunch is that this some problem with one or more of:

  • a mismatch in the points array and the precomputed triangulation indices
  • an issue with the vertex uploading assuming closed vs open
  • an issue with earcutr assuming closed vs open

While digging into that, I noticed that some (but not all) of the abstreet:geom::Polygon.points are closed. Do you have any strong feelings on whether abstreet::geom::Polygon.points should be closed?

Full disclosure is that more broadly I'm interested in making abstreet/geom more semantically consistent with georust/geo, in hopes of make future inter-op easier, but I'm also aware that these are the kinds of changes with no immediate benefit that could have a long tail of bugs in a system which is more or less currently working.

@dabreegster
Copy link
Contributor

Do you have any strong feelings on whether abstreet::geom::Polygon.points should be closed?

The intention is for them to be. But a flat list of points for a polygon with holes is meaningless -- to be more clear, Polygon.rings are all closed. The storage tries to be clever and avoid repetition right now -- per https://github.com/a-b-street/abstreet/blob/193672f19f4c288b4f8df0d3045c02f6ed8c0265/geom/src/polygon.rs#L66, if the polygon only has an exterior ring, then rings will be None and the points should be closed. If there are holes, then the points field is meaningless (it'll be all the points in the exterior ring, then each hole's points appended). The points() method should only return the exterior points, and they generally should be closed.

But all of the guarantees depend on how the Polygon is constructed. The "good" cases are with_holes, from_geojson, etc. There are a handful of callers of buggy_new where all we try to do is render. What's probably happening here is precomputed, where that guarantee breaks down. This code path uses usvg and then lyon to tesellate polygons. Last I checked, the order of points it hands back aren't usually closed -- probably SVGs we render often have holes, so we'd have to figure out how to get that out of lyon anyway.

Full disclosure is that more broadly I'm interested in making abstreet/geom more semantically consistent with georust/geo

Awesome! That's what I'd love to see too. A next (big) step would be to try to make geom::Polygon just store geo::Polygon and wrap APIs over it. Only rendering needs the triangulation, so the one caller of raw_for_rendering could do something different. There are probably smaller intermediate steps.

In an ideal world, we can make sure every single construction of a polygon happens from closed rings. But in case all we can get out of lyon is a triangulated mesh, I wonder what we should do otherwise.

Also related: the other big geom structure to rethink is PolyLine. It's effectively a geo::LineString that tries to enforce extra invariants, but that validation has been causing more and more problems lately. I've been working with GPS trajectory data lately where the points double back on themselves in all sorts of ways, but still being able to do some PolyLine operations would be nice.

@dabreegster
Copy link
Contributor

My unverified hunch is that this some problem with one or more of:

To test the theory that the problem is usvg + lyon feeding into Polygon::precomputed and not giving points in order or where first=last, try running A/B Street and looking at lanes/roads. That uses PolyLine::make_polygons, which also calls Polygon::precomputed, but there the points definitely are in order and closed

@michaelkirk
Copy link
Author

Ah! So the points returned from lyon are likely not even in any particular ordering WRT the winding of the polygon - it's basically just a bag of points that can be triangulated with indices.

It seems pretty unlikely that we'd be able to safely roundtrip that representation with geo.

@dabreegster
Copy link
Contributor

Maybe GeomBatch should have a way to store pretriangulated polygons, and not allow anything to access the Polygon. These cases from lyon should directly use that, and not use geom::Polygon at all. They likely break many of the methods in there now, and there shouldn't be many (any?) callers of those anyway. get_bounds is one of the few valid things to do, and scale/translate transformations.

@michaelkirk
Copy link
Author

That sounds plausible! I may (may!) have a go at this tomorrow.

@michaelkirk
Copy link
Author

Maybe GeomBatch should have a way to store pretriangulated polygons, and not allow anything to access the Polygon.

I think I'm having a go at something along these lines.

My basic idea was to have two types:

  • Polygon for a topologically conventional geometry (something that could feasibly be replaced by geo::Polygon)
  • Tessellation for a pre-triangulated entity suitable for rendering

It's going OK, but I think seeing it through could be a very large change. Here's my hack-and-slash WIP if you're curious:
https://github.com/michaelkirk/abstreet/tree/mkirk/tessellation

I think at the end of it, Polygon wouldn't have an indices field at all. And only at the point that you wanted to render the polygon (which you might not always want to), would you use earcutr to turn the Polygon into a Tessellation.

GeomBatch then, is a collection of Tessellations, not Polygons. I haven't gotten far enough to know if there will be any show stoppers with this approach, but it does seem like it's going to have far reaching implications that I might not have time to see through.

Does this approach seem reasonable?

@dabreegster
Copy link
Contributor

Approach sounds perfectly reasonable to me! The difficulties I would anticipate...

  • Callers of GeomBatch::consume... actually, scratch that. At a glance, all of them don't need a Polygon
  • rotate_around_batch_center currently calls Polygon::rotate_around, which is some pretty simple math that lives outside of geo. Tesselation will need to keep this
  • The handful of Polygon::buggy_new callers remain awkward, because some of them aren't just rendering. For example, convert_osm/src/osm_geom.rs tries to get Polygons to store for areas and buildings. (Mostly those are just rendered later, so you could maybe argue map_model could just store that instead?) But also, it might be fine to just start failing here if the multipolygons we're scraping from OSM aren't valid rings. I don't entirely remember what motivated adding this in; it's likely some more fundamental bug handling multipolygons.

So actually this hopefully shouldn't be too bad...

@dabreegster
Copy link
Contributor

If it's OK, I'm going to pick up your branch and try to push through. I think the migration steps could be:

  1. Introduce Tessellation, convert Polygon to it, use it for rendering
  2. Replace current uses of Polygon::buggy_new and Polygon::precomputed with it
  3. Make geom::Polygon just wrap geo

In your branch you were very carefully changing the API for places that directly construct a tessellation, but I won't attempt that until maybe a step 4. It's a huge refactor.

@dabreegster
Copy link
Contributor

I got too ambitious as usual! https://github.com/a-b-street/abstreet/tree/geo_polygon_too_quickly is a start, but I realized some more complications:

  • Circles and polylines becoming polygons should maybe still have a way to feed the pre-triangulated result forward, for performance. It's unnecessary to go through earcutr. Need to think through this, and also measure to see if it's actually going to be a problem.
  • All of the places unioning and intersecting polygons also need to be done carefully, because the result might not be a valid geo::Polygon. geo::Polygon kind of pretends multipolygons are single polygons sometimes, which is weird.

I do see a few smaller steps to peel off first, to make the migration easier. I'll see if I can get rid of a few places that construct geo::Polygons in strange ways.

dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
SVG icons that're circles, so just make a way to turn Bounds into a
Circle. #951
dabreegster referenced this issue in a-b-street/osm2streets Aug 16, 2022
dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
a-b-street/osm2streets#47. No behavioral effect
from this. For the moment, I'm pretending uncontrolled junctions are the
same as stop signs.

Also whittle down creators of strange polygons for #951. Some buildings
or areas may be affected, but no major problems seen yet.

Regenerating everything...
@michaelkirk
Copy link
Author

geo::Polygon kind of pretends multipolygons are single polygons sometimes, which is weird.

Can you expound on this? I'm not sure what you mean.

@dabreegster
Copy link
Contributor

I've got a few steps to remove callers that construct "bad" polygons. Then we can switch to tessellations for rendering, and consider step 3.

Can you expound on this? I'm not sure what you mean.

I'm mistaken. We always transform a geo::MultiPolygon into Vec<geom::Polygon>. I was thinking of geom::Polygon::union, which is just doing something bizarre. I'll whittle away the callers of this one too.

dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
a-b-street/osm2streets#47. No behavioral effect
from this. For the moment, I'm pretending uncontrolled junctions are the
same as stop signs.

Also whittle down creators of strange polygons for #951. Some buildings
or areas may be affected, but no major problems seen yet.

Regenerating everything...
dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
a-b-street/osm2streets#47. No behavioral effect
from this. For the moment, I'm pretending uncontrolled junctions are the
same as stop signs.

Also whittle down creators of strange polygons for #951. Some buildings
may be skipped on import now, but no major changes observed.

Regenerating everything...
dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
…any sidewalk corners that stop being rendered, so this is fine.
dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
…ering. #951

The goal is for every Polygon to consist of valid Rings, for a simpler
API and better georust integration.

Thanks to @michaelkirk for starting this!
dabreegster referenced this issue in a-b-street/abstreet Aug 16, 2022
dabreegster referenced this issue in a-b-street/abstreet Aug 17, 2022
dabreegster referenced this issue in a-b-street/abstreet Aug 17, 2022
…ering. #951

The goal is for every Polygon to consist of valid Rings, for a simpler
API and better georust integration.

Thanks to @michaelkirk for starting this!
dabreegster referenced this issue in a-b-street/abstreet Aug 17, 2022
dabreegster referenced this issue in a-b-street/abstreet Aug 30, 2022
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.
dabreegster referenced this issue in a-b-street/abstreet Aug 30, 2022
dabreegster referenced this issue in a-b-street/abstreet Sep 1, 2022
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.
dabreegster referenced this issue in a-b-street/abstreet Sep 2, 2022
dabreegster referenced this issue in a-b-street/abstreet Sep 3, 2022
dabreegster referenced this issue in a-b-street/abstreet Sep 3, 2022
Temporarily breaks the build...
dabreegster referenced this issue in a-b-street/abstreet Sep 3, 2022
And fix a bad Polygon::rectangle caller from widgetry.
dabreegster referenced this issue in a-b-street/abstreet Sep 3, 2022
Things like driveway trimming and snapping may slightly change near
buildings with holes. Not yet regenerating everything.
dabreegster referenced this issue in a-b-street/abstreet Sep 3, 2022
Tessellation when thickening Rings for outlines, since the result isn't
a valid Ring. #951
dabreegster referenced this issue in a-b-street/abstreet Sep 5, 2022
This was added to show people how direct the non-car trip is, but the UI
is overwhelming right now with 4 routes. Still let people see more info,
but don't overload them to start.
dabreegster referenced this issue in a-b-street/abstreet Sep 5, 2022
This was added to show people how direct the non-car trip is, but the UI
is overwhelming right now with 4 routes. Still let people see more info,
but don't overload them to start.
dabreegster referenced this issue in a-b-street/abstreet Sep 5, 2022
Tessellation when thickening Rings for outlines, since the result isn't
a valid Ring. #951
dabreegster referenced this issue in a-b-street/abstreet Sep 5, 2022
Tessellation when thickening Rings for outlines, since the result isn't
a valid Ring. #951
dabreegster referenced this issue in a-b-street/abstreet Sep 6, 2022
dabreegster referenced this issue in a-b-street/abstreet Sep 6, 2022
… add-on to the top-bar. #951

Sliiightly untested.
dabreegster referenced this issue in a-b-street/abstreet Sep 6, 2022
dabreegster referenced this issue in a-b-street/abstreet Sep 6, 2022
… add-on to the top-bar. #951

Sliiightly untested.
@dabreegster
Copy link
Contributor

dabreegster commented Sep 12, 2022

Remaining work before the polygon cleanup can be considered done:

  • Make polyline outlines be a proper Polygon with an outer and inner ring, then clean up tessellation -> geojson
  • Profile the Polygon -> Tessellation code and try out some ideas with earctur
  • Revert polygon.center change probably

dabreegster referenced this issue in a-b-street/abstreet Sep 12, 2022
…it slower, and most callers of center() just need something 'vaguely' in the middle of the polygon. #951

I didn't regenerate anything based on this change; not sure anything
would be affected anyway.
@dabreegster dabreegster transferred this issue from a-b-street/abstreet Nov 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants