geos icon indicating copy to clipboard operation
geos copied to clipboard

Buffer operation: use fallback to lower precision when result geometry is invalid

Open mngr777 opened this issue 3 years ago • 5 comments

Fix for "GEOSBufferWithStyle produce invalid geometry when quadsegs = 0" https://trac.osgeo.org/geos/ticket/1131

The issue can be solved by falling back on lower precision in BufferOp::computeGeometry(), but currently the result of BufferBuilder::buffer() is assumed to always be valid. I suggest adding validity check to BufferBuilder::buffer() and throwing when it fails, so the caller can retry with lower precision.

mngr777 avatar Feb 04 '22 13:02 mngr777

JTS recently had the buffer quadSegs behaviour improved/clarified: ​JTS-778.

So the first thing to do is to port that fix, and see if that solves this problem. If not can then investigate further.

dr-jts avatar Feb 04 '22 18:02 dr-jts

It turns out the fix from JTS-778 doesn't have any effect on this issue. (It's still worthwhile though, so I'll be making a PR for it).

At the moment this looks like some kind of robustness issue in GEOS which isn't in JTS. Perhaps due to a noding failure which for some reason isn't being detected. This will require more investigation.

I'm really reluctant to accept the fix in this PR, though, because running validation is costly and it penalizes every call to buffer.

dr-jts avatar Feb 04 '22 19:02 dr-jts

Yes, this is a robustness issue. I made a diagram showing layout of vertices in BufferBuilder::edgeList at this line: https://github.com/libgeos/geos/blob/4526237dfadd47eb4fc2f54a3578d45e6d63b9d9/src/operation/buffer/BufferBuilder.cpp#L429

Labels are edge index / point index in edge, image is not to scale, close points are really close (see second image). I think the exact cause here is point [0/9] gets connected to [0/10, 1/0, 4/1, 5/0, 6/2] that is not leftmost in the group of 4 close points, and that throws off edge depth calculation for the graph (I didn't look at all depth values though). edge-points

Here are coordinates of the 4-point group in the middle, where multiple buffer lines intersect: edge-points-coords

JTS documetation states that BufferOp result is always valid, so if intention is the same in GEOS, it seems that either BufferBuilder::buffer() (or BufferOp methods that call it) should check for validity and throw, or BufferBuilder::buffer() should be somehow fixed and assertion added that checks for validity. I understand the efficiency concern though.

I may be missing something but I don't see a good way to handle this situation using C API, since there's no precision parameter for buffer operation.

mngr777 avatar Feb 04 '22 21:02 mngr777

The raw buffer offset curve linework can be visualized using the JTS TestBuilder Magnify Topology mode: image

I suspect that this is not being correctly noded in GEOS, and so the buffer topology building produces an incorrect result. In JTS this works, so perhaps there's some difference in the noder code, or (more likely) in the robust math.

Definitely the buffer operation should always produce valid output. So this needs to be fixed - somehow. In JTS the noding is checked post-processing, and if it fails it allows the buffer code to drop through to a fixed-precision retry. Perhaps for some reason this isn't being caught in GEOS.

(In fact, perhaps buffer should always use a fine fixed-precision grid, since the result is almost always a fairly coarse approximation anyway. This is on the list as an upgrade for JTS - but it's a fairly large task to wade into).

dr-jts avatar Feb 04 '22 22:02 dr-jts

Further investigation reveals the following:

  • in JTS, the fast noding produce an invalid arrangement (not fully noded), but the buffer topology building finds an inconsistency because of this, throws, and thus invokes snap-rounding which completes successfully
  • In GEOS, the noding is wrong, but for some reason this doesn't trigger an error in topology-building, so the buffer is built incorrectly

Not sure that trying to fix the GEOS topology building will be very easy. So the easy thing to do is to in fact check whether buffer is valid, as this PR suggests. In fact, buffer output tends to be simpler than the input, so this may not be a huge performance hit. (It will certainly be faster than checking the noding arrangement, since that can be very large).

dr-jts avatar Feb 04 '22 23:02 dr-jts

So... do we actually want a post-buffer validity check?

pramsey avatar May 04 '23 03:05 pramsey

I think the answer is that we do not want a post-buffer validity check, people can do that themselves if it's something they care about.

pramsey avatar Jun 07 '23 16:06 pramsey