mirror of https://github.com/apache/lucene.git
LUCENE-10470: [Tessellator] Fix some failing polygons due to collinear edges (#756)
Check if polygon has been successfully tessellated before we fail (we are failing some valid tessellations) and allow filtering edges that fold on top of the previous one
This commit is contained in:
parent
2b20b3f2ca
commit
5d3ab09676
|
@ -152,6 +152,9 @@ Bug Fixes
|
|||
* LUCENE-10529: Properly handle when TestTaxonomyFacetAssociations test case randomly indexes
|
||||
no documents instead of throwing an NPE. (Greg Miller)
|
||||
|
||||
* LUCENE-10470: Check if polygon has been successfully tessellated before we fail (we are failing some valid
|
||||
tessellations) and allow filtering edges that fold on top of the previous one. (Ignacio Vera)
|
||||
|
||||
Build
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -827,7 +827,8 @@ public final class Tessellator {
|
|||
}
|
||||
searchNode = searchNode.next;
|
||||
} while (searchNode != start);
|
||||
return false;
|
||||
// if there is some area left, we failed
|
||||
return signedArea(start, start) == 0;
|
||||
}
|
||||
|
||||
/** Computes if edge defined by a and b overlaps with a polygon edge * */
|
||||
|
@ -1102,6 +1103,12 @@ public final class Tessellator {
|
|||
|
||||
/** Determine whether the polygon defined between node start and node end is CW */
|
||||
private static boolean isCWPolygon(final Node start, final Node end) {
|
||||
// The polygon must be CW
|
||||
return (signedArea(start, end) < 0) ? true : false;
|
||||
}
|
||||
|
||||
/** Determine the signed area between node start and node end */
|
||||
private static double signedArea(final Node start, final Node end) {
|
||||
Node next = start;
|
||||
double windingSum = 0;
|
||||
do {
|
||||
|
@ -1111,8 +1118,7 @@ public final class Tessellator {
|
|||
next.getX(), next.getY(), next.next.getX(), next.next.getY(), end.getX(), end.getY());
|
||||
next = next.next;
|
||||
} while (next.next != end);
|
||||
// The polygon must be CW
|
||||
return (windingSum < 0) ? true : false;
|
||||
return windingSum;
|
||||
}
|
||||
|
||||
private static final boolean isLocallyInside(final Node a, final Node b) {
|
||||
|
@ -1291,10 +1297,12 @@ public final class Tessellator {
|
|||
// we can filter points when:
|
||||
// 1. they are the same
|
||||
// 2.- each one starts and ends in each other
|
||||
// 3.- they are co-linear and both edges have the same value in .isNextEdgeFromPolygon
|
||||
// 3.- they are collinear and both edges have the same value in .isNextEdgeFromPolygon
|
||||
// 4.- they are collinear and second edge returns over the first edge
|
||||
if (isVertexEquals(node, nextNode)
|
||||
|| isVertexEquals(prevNode, nextNode)
|
||||
|| (prevNode.isNextEdgeFromPolygon == node.isNextEdgeFromPolygon
|
||||
|| ((prevNode.isNextEdgeFromPolygon == node.isNextEdgeFromPolygon
|
||||
|| isPointInLine(prevNode, node, nextNode.getX(), nextNode.getY()))
|
||||
&& area(
|
||||
prevNode.getX(),
|
||||
prevNode.getY(),
|
||||
|
|
|
@ -128,10 +128,19 @@ public class TestTessellator extends LuceneTestCase {
|
|||
public void testInvalidPolygonIntersects() throws Exception {
|
||||
String wkt = "POLYGON((0 0, 1 1, 0 1, 1 0, 0 0))";
|
||||
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
|
||||
{
|
||||
IllegalArgumentException ex =
|
||||
expectThrows(IllegalArgumentException.class, () -> Tessellator.tessellate(polygon, true));
|
||||
assertEquals("Polygon self-intersection at lat=0.5 lon=0.5", ex.getMessage());
|
||||
}
|
||||
{
|
||||
IllegalArgumentException ex =
|
||||
expectThrows(
|
||||
IllegalArgumentException.class, () -> Tessellator.tessellate(polygon, false));
|
||||
assertEquals(
|
||||
"Unable to Tessellate shape. Possible malformed shape detected.", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testInvalidPolygonOverlap() throws Exception {
|
||||
// holes are equal
|
||||
|
@ -143,9 +152,19 @@ public class TestTessellator extends LuceneTestCase {
|
|||
+ " (6.0391557 52.0929189, 6.0388667 52.0928373, 6.0387045 52.0928107, 6.038578 52.0927855, 6.0384897 52.0927195, 6.0384626 52.0927036, 6.0384412 52.0926911, 6.0382642 52.0926086, 6.0380309 52.092529, 6.0377877 52.0924683, 6.0377571 52.0924499, 6.0377263 52.0924189, 6.037857 52.0923747, 6.0383203 52.0923097, 6.0385012 52.0922528, 6.0385416 52.0922588, 6.0385632 52.0923458, 6.0386452 52.0924386, 6.0387875 52.0925001, 6.0391495 52.0926998, 6.0393437 52.0928496, 6.0393774 52.0928918, 6.0393715 52.092914, 6.0393239 52.0929308, 6.039246 52.0929349, 6.0391557 52.0929189),"
|
||||
+ " (6.0377263 52.0924189, 6.0377571 52.0924499, 6.0377877 52.0924683, 6.0380309 52.092529, 6.0382642 52.0926086, 6.0384412 52.0926911, 6.0384626 52.0927036, 6.0384897 52.0927195, 6.038578 52.0927855, 6.0387045 52.0928107, 6.0388667 52.0928373, 6.0391557 52.0929189, 6.039246 52.0929349, 6.0393239 52.0929308, 6.0393715 52.092914, 6.0393774 52.0928918, 6.0393437 52.0928496, 6.0391495 52.0926998, 6.0387875 52.0925001, 6.0386452 52.0924386, 6.0385632 52.0923458, 6.0385416 52.0922588, 6.0385012 52.0922528, 6.0383203 52.0923097, 6.037857 52.0923747, 6.0377263 52.0924189))";
|
||||
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
|
||||
{
|
||||
IllegalArgumentException ex =
|
||||
expectThrows(IllegalArgumentException.class, () -> Tessellator.tessellate(polygon, true));
|
||||
assertEquals("Polygon ring self-intersection at lat=52.0924189 lon=6.0377263", ex.getMessage());
|
||||
assertEquals(
|
||||
"Polygon ring self-intersection at lat=52.0924189 lon=6.0377263", ex.getMessage());
|
||||
}
|
||||
{
|
||||
IllegalArgumentException ex =
|
||||
expectThrows(
|
||||
IllegalArgumentException.class, () -> Tessellator.tessellate(polygon, false));
|
||||
assertEquals(
|
||||
"Unable to Tessellate shape. Possible malformed shape detected.", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testLUCENE8559() throws Exception {
|
||||
|
@ -737,6 +756,60 @@ public class TestTessellator extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testComplexPolygon45() throws Exception {
|
||||
String geoJson = GeoTestUtil.readShape("lucene-10470.geojson.gz");
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
|
||||
for (int i = 0; i < polygons.length; i++) {
|
||||
List<Tessellator.Triangle> tessellation =
|
||||
Tessellator.tessellate(polygons[i], random().nextBoolean());
|
||||
// calculate the area of big polygons have numerical error
|
||||
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
|
||||
for (Tessellator.Triangle t : tessellation) {
|
||||
checkTriangleEdgesFromPolygon(polygons[i], t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testComplexPolygon46() throws Exception {
|
||||
String wkt = GeoTestUtil.readShape("lucene-10470.wkt.gz");
|
||||
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
|
||||
List<Tessellator.Triangle> tessellation =
|
||||
Tessellator.tessellate(polygon, random().nextBoolean());
|
||||
// calculate the area of big polygons have numerical error
|
||||
assertEquals(area(polygon), area(tessellation), 1e-11);
|
||||
for (Tessellator.Triangle t : tessellation) {
|
||||
checkTriangleEdgesFromPolygon(polygon, t);
|
||||
}
|
||||
}
|
||||
|
||||
public void testComplexPolygon47() throws Exception {
|
||||
String geoJson = GeoTestUtil.readShape("lucene-10470-2.geojson.gz");
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
|
||||
for (int i = 0; i < polygons.length; i++) {
|
||||
List<Tessellator.Triangle> tessellation =
|
||||
Tessellator.tessellate(polygons[i], random().nextBoolean());
|
||||
// calculate the area of big polygons have numerical error
|
||||
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
|
||||
for (Tessellator.Triangle t : tessellation) {
|
||||
checkTriangleEdgesFromPolygon(polygons[i], t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nightly
|
||||
public void testComplexPolygon48() throws Exception {
|
||||
String geoJson = GeoTestUtil.readShape("lucene-10470-3.geojson.gz");
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
|
||||
for (int i = 0; i < polygons.length; i++) {
|
||||
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygons[i], true);
|
||||
// calculate the area of big polygons have numerical error
|
||||
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
|
||||
for (Tessellator.Triangle t : tessellation) {
|
||||
checkTriangleEdgesFromPolygon(polygons[i], t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPolygon(String wkt) throws Exception {
|
||||
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
|
||||
List<Tessellator.Triangle> tessellation =
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue