LUCENE-10563: Fix failure to tessellate complex polygon (#933)

This commit is contained in:
Craig Taverner 2022-06-08 10:58:40 +02:00 committed by GitHub
parent b5795db0cf
commit 1dceff12c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 18 deletions

View File

@ -95,6 +95,8 @@ Bug Fixes
* LUCENE-10598: SortedSetDocValues#docValueCount() should be always greater than zero. (Lu Xugang)
* LUCENE-10563: Fix failure to tessellate complex polygon (Craig Taverner)
Other
---------------------

View File

@ -800,9 +800,8 @@ public final class Tessellator {
final boolean mortonOptimized) {
// Search for a valid diagonal that divides the polygon into two.
Node searchNode = start;
Node nextNode;
do {
nextNode = searchNode.next;
Node nextNode = searchNode.next;
Node diagonal = nextNode.next;
while (diagonal != searchNode.previous) {
if (searchNode.idx != diagonal.idx && isValidDiagonal(searchNode, diagonal)) {
@ -1093,6 +1092,8 @@ public final class Tessellator {
&& isIntersectingPolygon(a, a.getX(), a.getY(), b.getX(), b.getY()) == false
&& isLocallyInside(a, b)
&& isLocallyInside(b, a)
&& isLocallyInside(a.previous, b)
&& isLocallyInside(b.next, a)
&& middleInsert(a, a.getX(), a.getY(), b.getX(), b.getY())
// make sure we don't introduce collinear lines
&& area(a.previous.getX(), a.previous.getY(), a.getX(), a.getY(), b.getX(), b.getY()) != 0

View File

@ -17,6 +17,7 @@
package org.apache.lucene.geo;
import static org.apache.lucene.tests.geo.GeoTestUtil.nextBoxNotCrossingDateline;
import static org.hamcrest.Matchers.equalTo;
import java.text.ParseException;
import java.util.List;
@ -704,13 +705,13 @@ public class TestTessellator extends LuceneTestCase {
public void testComplexPolygon42() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-9417.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
for (int i = 0; i < polygons.length; i++) {
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygons[i], random().nextBoolean());
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygons[i], t);
checkTriangleEdgesFromPolygon(polygon, t);
}
}
}
@ -759,13 +760,13 @@ 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++) {
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygons[i], random().nextBoolean());
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygons[i], t);
checkTriangleEdgesFromPolygon(polygon, t);
}
}
}
@ -785,13 +786,13 @@ public class TestTessellator extends LuceneTestCase {
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++) {
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygons[i], random().nextBoolean());
Tessellator.tessellate(polygon, random().nextBoolean());
// calculate the area of big polygons have numerical error
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygons[i], t);
checkTriangleEdgesFromPolygon(polygon, t);
}
}
}
@ -800,16 +801,82 @@ public class TestTessellator extends LuceneTestCase {
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);
for (Polygon polygon : polygons) {
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon, true);
// calculate the area of big polygons have numerical error
assertEquals(area(polygons[i]), area(tessellation), 1e-11);
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygons[i], t);
checkTriangleEdgesFromPolygon(polygon, t);
}
}
}
public void testComplexPolygon49() throws Exception {
String wkt =
"POLYGON((77.500 13.500, 77.550 13.500, 77.530 13.470, 77.570 13.470,"
+ "77.550 13.500, 77.600 13.500, 77.600 13.400, 77.500 13.400, 77.500 13.500))";
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation =
Tessellator.tessellate(polygon, random().nextBoolean());
assertEquals(area(polygon), area(tessellation), 1e-11);
for (Tessellator.Triangle t : tessellation) {
checkTriangleEdgesFromPolygon(polygon, t);
}
}
public void testComplexPolygon50() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10563-1.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
assertThat("Only one polygon", polygons.length, equalTo(1));
Polygon polygon = polygons[0];
List<Tessellator.Triangle> tessellation = Tessellator.tessellate(polygon, true);
// 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 testComplexPolygon51() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10563-2.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
assertThat("Only one polygon", polygons.length, equalTo(1));
Polygon polygon = polygons[0];
boolean checkSelfIntersections = random().nextBoolean();
IllegalArgumentException ex =
expectThrows(
IllegalArgumentException.class,
() -> Tessellator.tessellate(polygon, checkSelfIntersections));
String error =
checkSelfIntersections
? "Polygon self-intersection at lat=2.8440144262027296 lon=177.96701124393607"
: "Unable to Tessellate shape. Possible malformed shape detected.";
assertEquals(
"Expected specific error depending on checkSelfIntersections=" + checkSelfIntersections,
error,
ex.getMessage());
}
public void testComplexPolygon52() throws Exception {
String geoJson = GeoTestUtil.readShape("lucene-10563-3.geojson.gz");
Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
assertThat("Only one polygon", polygons.length, equalTo(1));
Polygon polygon = polygons[0];
boolean checkSelfIntersections = random().nextBoolean();
IllegalArgumentException ex =
expectThrows(
IllegalArgumentException.class,
() -> Tessellator.tessellate(polygon, checkSelfIntersections));
String error =
checkSelfIntersections
? "Polygon self-intersection at lat=-11.22876335157631 lon=126.94854431224186"
: "Unable to Tessellate shape. Possible malformed shape detected.";
assertEquals(
"Expected specific error depending on checkSelfIntersections=" + checkSelfIntersections,
error,
ex.getMessage());
}
private void checkPolygon(String wkt) throws Exception {
Polygon polygon = (Polygon) SimpleWKTShapeParser.parse(wkt);
List<Tessellator.Triangle> tessellation =