LUCENE-10654: Fix ShapeDocValue Bounding Box failure (#1066)

The base spatial test case may create invalid self crossing polygons. These
polygons are cleaned by the tessellator which may result in an inconsistent
bounding box between the tessellated shape and the original, invalid, geometry.
This commit fixes the shape doc value test case to compute the bounding box from
the cleaned geometry instead of relying on the, potentially invalid, original
geometry.

Signed-off-by: Nicholas Walter Knize <nknize@apache.org>
This commit is contained in:
Nick Knize 2022-08-12 10:54:22 -05:00 committed by GitHub
parent fe8d11254a
commit 543910d900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 3 deletions

View File

@ -194,7 +194,7 @@ public final class Tessellator {
sortByMorton(outerNode);
}
}
if (checkSelfIntersections) {
if (checkSelfIntersections == true) {
checkIntersection(outerNode, mortonOptimized);
}
// Calculate the tessellation using the doubly LinkedList.
@ -1060,6 +1060,7 @@ public final class Tessellator {
return isPointInLine(a, b, point.getX(), point.getY());
}
/** returns true if the lon, lat point is colinear w/ the provided a and b point */
private static boolean isPointInLine(
final Node a, final Node b, final double lon, final double lat) {
final double dxc = lon - a.getX();

View File

@ -56,7 +56,7 @@ public class TestShapeDocValues extends LuceneTestCase {
public void testLatLonPolygonBBox() {
Polygon p = GeoTestUtil.nextPolygon();
Rectangle expected = new Rectangle(p.minLat, p.maxLat, p.minLon, p.maxLon);
Rectangle expected = (Rectangle) computeBoundingBox(p);
LatLonShapeDocValuesField dv = LatLonShape.createDocValueField(FIELD_NAME, p);
assertEquals(expected.minLat, dv.getBoundingBox().minLat, TOLERANCE);
assertEquals(expected.maxLat, dv.getBoundingBox().maxLat, TOLERANCE);
@ -66,7 +66,7 @@ public class TestShapeDocValues extends LuceneTestCase {
public void testXYPolygonBBox() {
XYPolygon p = (XYPolygon) BaseXYShapeTestCase.ShapeType.POLYGON.nextShape();
XYRectangle expected = new XYRectangle(p.minX, p.maxX, p.minY, p.maxY);
XYRectangle expected = (XYRectangle) computeBoundingBox(p);
XYShapeDocValuesField dv = XYShape.createDocValueField(FIELD_NAME, p);
assertEquals(expected.minX, dv.getBoundingBox().minX, TOLERANCE);
assertEquals(expected.maxX, dv.getBoundingBox().maxX, TOLERANCE);
@ -132,6 +132,51 @@ public class TestShapeDocValues extends LuceneTestCase {
return createPoint.apply(numXPly / totalSignedArea, numYPly / totalSignedArea);
}
/**
* compute the bounding box from the tessellation; test utils may create self crossing polygons
* cleaned by the tessellator
*/
private Geometry computeBoundingBox(Geometry p) {
List<ShapeField.DecodedTriangle> tess = getTessellation(p);
IntFunction<Double> decodeX =
p instanceof Polygon
? (x) -> GeoEncodingUtils.decodeLongitude(x)
: (x) -> (double) XYEncodingUtils.decode(x);
IntFunction<Double> decodeY =
p instanceof Polygon
? (y) -> GeoEncodingUtils.decodeLatitude(y)
: (y) -> (double) XYEncodingUtils.decode(y);
BiFunction<Double[], Double[], Geometry> createRectangle =
p instanceof Polygon
? (min, max) -> new Rectangle(min[1], max[1], min[0], max[0])
: (min, max) ->
new XYRectangle(
min[0].floatValue(),
max[0].floatValue(),
min[1].floatValue(),
max[1].floatValue());
double ax, bx, cx;
double ay, by, cy;
double minX = Double.MAX_VALUE;
double minY = Double.MAX_VALUE;
double maxX = -Double.MAX_VALUE;
double maxY = -Double.MAX_VALUE;
for (ShapeField.DecodedTriangle t : tess) {
ax = decodeX.apply(t.aX);
ay = decodeY.apply(t.aY);
bx = decodeX.apply(t.bX);
by = decodeY.apply(t.bY);
cx = decodeX.apply(t.cX);
cy = decodeY.apply(t.cY);
minX = Math.min(minX, Math.min(ax, Math.min(bx, cx)));
maxX = Math.max(maxX, Math.max(ax, Math.max(bx, cx)));
minY = Math.min(minY, Math.min(ay, Math.min(by, cy)));
maxY = Math.max(maxY, Math.max(ay, Math.max(by, cy)));
}
return createRectangle.apply(new Double[] {minX, minY}, new Double[] {maxX, maxY});
}
public void testExplicitLatLonPolygonCentroid() throws Exception {
String mp = "POLYGON((-80 -10, -40 -10, -40 10, -80 10, -80 -10))";
Polygon p = (Polygon) SimpleWKTShapeParser.parse(mp);