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

use RingClipper for polygon clip in RectangeIntersection #605

Conversation

kalenikaliaksandr
Copy link

There is a bug in ST_ClipByBox2D.

select st_astext(st_clipbybox2d('POLYGON((10 0, 10 60, 50 20, 90 60, 90 0, 10 0))'::geometry, 'POLYGON((30 20, 30 40, 70 40, 70 20, 30 20))'::geometry));
┌────────────────────────────────────┐
│             st_astext              │
├────────────────────────────────────┤
│ POLYGON((30 40,50 20,70 40,30 40)) │
└────────────────────────────────────┘
(1 row)

In this example input geometry has coincident segments along the sides of clip rectangle. It is noted that output might be self-intersecting geometry but for this case produced polygon contains part of rectangle that doesn't has overlap with input geometry. The bug is located in the code that tries to build polygon from clipped lines and clip rectangle.

In this PR I replaced part of RectangleIntersection class responsible for polygon clip with RingClipper which still might produce invalid geometry but doesn't suffer from mentioned bug in current implementation.

@dr-jts
Copy link
Contributor

dr-jts commented May 11, 2022

OverlayNG has an optimization for intersection, which should provide very good performance for intersection with rectangles. Any idea if ST_ClipByBox2D is still faster?

@robe2 robe2 requested review from pramsey and dbaston May 16, 2022 01:39
@pramsey
Copy link
Member

pramsey commented May 16, 2022

I think we might need to actually benchmark this out, since if NG is just faster for rectangles, the best thing to do us remove all the old rectangle code and delegate to NG.

@dr-jts
Copy link
Contributor

dr-jts commented May 16, 2022

I think we might need to actually benchmark this out, since if NG is just faster for rectangles, the best thing to do us remove all the old rectangle code and delegate to NG.

Absolutely. I can take this on.

@kalenikaliaksandr
Copy link
Author

I tested from PostGIS using following query select count(*) from (select ST_Subdivide(geom, 30) from large_country_boundary) q;
using PostGIS built from master that uses OverlayNG to clip polygon in ST_Subdivide: 1651,973 ms
using modified PostGIS that uses GEOSClipByRect to clip polygon in ST_Subdivide: 168,398 ms

GEOSClipByRect is significantly faster but subdivision contains invalid polygons so maybe it's worth using NG for clip despite worse performance.

@dr-jts
Copy link
Contributor

dr-jts commented May 16, 2022

GEOSClipByRect is significantly faster but subdivision contains invalid polygons so maybe it's worth using NG for clip despite worse performance.

Makes sense to me. The output from ST_Subdivide should be valid, since it is often used for further querying.

@kalenikaliaksandr
Copy link
Author

kalenikaliaksandr commented May 16, 2022

Makes sense to me. The output from ST_Subdivide should be valid, since it is often used for further querying.

ST_Subdivide doesn't use GEOSClipByRect with current implementation, it uses OverlayNG. I made ST_Subdivide to use GEOSClipByRect for my benchmark.

if ST_ClipByBox2D is still faster?

Yes, GEOSClipByRect is still faster than OverlayNG. At least for my test input. But sometimes it produces invalid geometries. Actually it's noted in PostGIS documentation that invalid geometries might be produced https://postgis.net/docs/ST_ClipByBox2D.html.

Copy link
Member

@pramsey pramsey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This either needs to be a more thorough-going fix to the RectangleIntersection that does not produce new and exciting invalidities while getting rid of other ones, or to be abandoned on the principle of "the devil you know".

@@ -1565,7 +1565,7 @@ template<> template<> void object::test<139>
{
doClipTest(
"POLYGON ((-15 -15,-15 15,15 15,15 -15,-15 -15),(-5 5,-5 -5,5 -5,5 5,-5 5))",
"POLYGON ((0 5,0 10,10 10,10 0,5 0,5 5,0 5))",
"POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (0 0, 5 0, 5 5, 0 5, 0 0))",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are pretty big changes in behaviour...

@@ -1630,7 +1630,7 @@ template<> template<> void object::test<205>
"(-5 6,5 6,5 4,-5 4,-5 6)" // CW
")";
const char* exp =
"POLYGON((0 8,8 8, 8 2, 0 2, 0 4, 5 4, 5 6, 0 6, 0 8))";
"POLYGON ((0 2, 0 8, 8 8, 8 2, 0 2), (0 4, 5 4, 5 6, 0 6, 0 4))";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of holes-over-shells cases come out of this change.

@@ -136,7 +136,7 @@ template<> template<> void object::test<10>
{
geom1_ = GEOSGeomFromWKT("POLYGON((0 0, 0 30, 30 30, 30 0, 0 0),(10 10, 20 10, 20 20, 10 20, 10 10))");
geom2_ = GEOSClipByRect(geom1_, 10, 10, 20, 20);
isEqual(geom2_, "POLYGON EMPTY");
isEqual(geom2_, "POLYGON((10 20, 20 20, 20 10, 10 10, 10 20), (10 10, 20 10, 20 20, 10 20, 10 10))");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is way worse than before... the result is a hole exactly on the shell. Which is, yes, empty, but far less legibly than what came before. Is the juice worth the squeeze here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pramsey thank you for review.

This is way worse than before... the result is a hole exactly on the shell. Which is, yes, empty, but far less legibly than what came before. Is the juice worth the squeeze here?

right, with these changes there are plenty of cases when geometry becomes invalid after clip whereas with current implementation it's fine but all these geometries are possible to fix with ST_MakeValid while the example I provided in description is unrepairable because polygon is constructed from wrong part of rectangle.

That is what happens when subdivision is done using current ST_ClipByBox2D:
Screenshot_20220525_002510

So I would prefer to have more cases where invalid geometries are produces but these geometries are repairable over function that almost always ok but sometimes outputs completely broken geometry.

For me it looks like making ST_ClipByBox2D use OverlayNG like @dr-jts suggested is the best option here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it seems validity is the highest order concern. Although aren't we already using OverlayNG in subdivide?

Copy link
Author

@kalenikaliaksandr kalenikaliaksandr May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, OverlayNG is already used in subdivide. I made it to use ST_ClipByBox2D for my testing purposes, it does plenty of clip operations which makes it easier to find these kind of issue.

@pramsey
Copy link
Member

pramsey commented May 4, 2023

This seems to have aged out. There is perhaps a use case for rectangle (or convex?) clipping if it can be made reliable, but I don't know that this is it?

@pramsey pramsey closed this May 4, 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

Successfully merging this pull request may close these issues.

3 participants