Esri/geometry-api-java

buffer on small polygons is returning null

jagill opened this issue · 6 comments

Buffering very small polygons (which a user obtained by buffering a Point by a small amount) returns null. For example,

    OGCGeometry p = OGCGeometry.fromText(
        "POLYGON ((177.0 64.0, 177.0000000001 64.0, 177.0000000001 64.0000000001, 177.0 64.0000000001, 177.0 64.0))"
    );
    OGCGeometry buffered = p.buffer(0.01);
    assertIsNull(buffered);

During the simplification step, the CrackAndCluster._do() collapses nearby points, which includes these nearby vertices. Instead of collapsing it to a Point, it collapses it to a Polygon with no rings, which eventually bubbles up as a null.

This, of course, causes an NPE when used.

cc @mbasmanova

@jagill Buffer should have returned an empty polygon in that case instead of null. We don't allow changing geometry type in the buffer operation.
Also, if polygon collapses and becomes empty, then it is not a simple polygon and it is expected for it to disappear (see OperatorSimplify help). However, we should not return null.

The buffer operation shouldn't return an empty polygon: the original "point-ish" polygon buffered should be something similar to a buffered point. Returning empty is non-intuitive; consider:

OGCGeometry p = OGCGeometry.fromText("POINT (177.0 64.0)");
p.buffer(0.000000001).buffer(0.01);

That should be similar to p.buffer(0.01); if the first buffer returns an empty geometry, the result will also be empty, which is weird.

@jagill I understand that there is a logical issue here. However this is how our topological operations work. They expect topologically simple input and should make topologicaly simple output. And the OperatorBuffer only outputs polygons and never points or lines.

OGCGeometry uses spatial reference 4326 that has tolerace of 1e-8 assigned in this library. So things start to be considered non-simple and will collapse when getting close to 1e-8.
Actually I don't remember why we assigned spatial reference to the OGCGeometry at all. The engine could use null spatial reference that would compute a much smaller tolerance value from the geometry boundaries and the buffer would not collapse for 1e-9 distance.

So there are two things here.

  1. The buffer operation should not return null.
  2. The buffer maybe expected to work with degenerate inputs and outputs and allow things to collaps to lines and points.
    I can fix 1 sooner. I may take into 2 when I have time.

I'm confused; why would the buffer operation return a line or point? We are buffering a polygon, and thus it should return a polygon. It's the internal simplification step that is reducing the polygon to something else (point/empty/null), which then the buffer has trouble with. When you say that the buffer operation would return a line or point, then I'm worried that we're talking about different things!

If p is a polygon, then p.buffer(x) should be a polygon, right? Or is the statement that if p is smaller than tolerance, then the buffering behavior is undefined?

@jagill Buffer expects topologically simple input and should produce a topologicaly simple output. When a point is buffered with 1e-9 distance, the result polygon is smaller than the tolerance, thus it is considered non-simple, and is deleted.

Verified fix.