Skip to content

LUCENE-10470: [Tessellator] Prevent bridges that introduce collinear edges #756

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

Merged
merged 25 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0c35e90
prevent holly bridges for hole elimination to be parallel with edges …
iverase Mar 17, 2022
34f850e
add comment
iverase Mar 21, 2022
e89b37d
make sure we don't introduce collinear lines
iverase Mar 21, 2022
1e5b279
spotless
iverase Mar 21, 2022
680ebf2
add entry in changes.txt
iverase Mar 21, 2022
13ad06b
iter
iverase Mar 28, 2022
59d1c4c
Just check if there is some area left
iverase Apr 1, 2022
70416dc
Merge branch 'main' into LUCENE-10470
iverase Apr 1, 2022
26f626a
update entry in changes
iverase Apr 1, 2022
6a0e535
update entry in changes
iverase Apr 1, 2022
732aec7
update entry in changes
iverase Apr 1, 2022
5f33958
fix
iverase Apr 1, 2022
f21756c
make sure we still fail invalid polygons
iverase Apr 2, 2022
b699e2b
Allow filtering edges that return over the previous edge.
iverase Apr 5, 2022
51db827
Merge remote-tracking branch 'upstream/main' into LUCENE-10470
iverase Apr 5, 2022
37558b6
Fix issue when a bridge is connected to a vertex that contains more t…
iverase Apr 20, 2022
0870f49
Merge remote-tracking branch 'upstream/main' into LUCENE-10470
iverase Apr 20, 2022
0d961ea
revert change
iverase Apr 20, 2022
2347e18
Merge remote-tracking branch 'upstream/main' into LUCENE-10470
iverase Apr 22, 2022
c150c7a
Revert "revert change"
iverase Apr 25, 2022
69e7a97
Revert "Fix issue when a bridge is connected to a vertex that contain…
iverase Apr 25, 2022
d924091
Merge remote-tracking branch 'upstream/main' into LUCENE-10470
iverase Apr 25, 2022
9659a1e
Merge branch 'main' into LUCENE-10470
iverase Apr 27, 2022
5b9199d
remove unnecessary comment
iverase Apr 27, 2022
213e388
update CHANGES.txt
iverase Apr 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lucene/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------

Expand Down
18 changes: 13 additions & 5 deletions lucene/core/src/java/org/apache/lucene/geo/Tessellator.java
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,8 @@ private static final boolean splitEarcut(
}
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 * */
Expand Down Expand Up @@ -1102,6 +1103,12 @@ && area(a.next.getX(), a.next.getY(), a.getX(), a.getY(), b.getX(), b.getY()) !=

/** 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 {
Expand All @@ -1111,8 +1118,7 @@ private static boolean isCWPolygon(final Node start, final Node end) {
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) {
Expand Down Expand Up @@ -1291,10 +1297,12 @@ private static final Node filterPoints(final Node start, Node end) {
// 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(),
Expand Down
85 changes: 79 additions & 6 deletions lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,18 @@ public void testLUCENE8534() throws ParseException {
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, 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 {
Expand All @@ -143,9 +152,19 @@ public void testInvalidPolygonOverlap() throws Exception {
+ " (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());
{
IllegalArgumentException ex =
expectThrows(IllegalArgumentException.class, () -> Tessellator.tessellate(polygon, true));
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 {
Expand Down Expand Up @@ -737,6 +756,60 @@ public void testComplexPolygon44() throws Exception {
}
}

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 =
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.