From 1dceff12c8bb764255927be55e46f4d435f47aed Mon Sep 17 00:00:00 2001 From: Craig Taverner Date: Wed, 8 Jun 2022 10:58:40 +0200 Subject: [PATCH] LUCENE-10563: Fix failure to tessellate complex polygon (#933) --- lucene/CHANGES.txt | 2 + .../org/apache/lucene/geo/Tessellator.java | 5 +- .../apache/lucene/geo/TestTessellator.java | 99 +++++++++++++++--- .../tests/geo/lucene-10563-1.geojson.gz | Bin 0 -> 541 bytes .../tests/geo/lucene-10563-2.geojson.gz | Bin 0 -> 362 bytes .../tests/geo/lucene-10563-3.geojson.gz | Bin 0 -> 358 bytes 6 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-1.geojson.gz create mode 100644 lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-2.geojson.gz create mode 100644 lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-3.geojson.gz diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index f3de66e7409..a9e0c3822f8 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -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 --------------------- diff --git a/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java b/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java index 4a6a8fc26a8..cfdd3efcb21 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java @@ -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 diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java b/lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java index 1f2220c3754..1c491bf8ae2 100644 --- a/lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java +++ b/lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java @@ -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 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 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 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 tessellation = Tessellator.tessellate(polygons[i], true); + for (Polygon polygon : polygons) { + List 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 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 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 tessellation = diff --git a/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-1.geojson.gz b/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-1.geojson.gz new file mode 100644 index 0000000000000000000000000000000000000000..5f283a38e1877d13e7f2193cdc0816ea2002a2c3 GIT binary patch literal 541 zcmV+&0^(Ur-z@8#~+`+K74$< z|LJ=l{L%EXF2B?CPH&(VAOkD~5c#ld;SD2mQv)efih{iC-5tg`T3gdzWq4ZD%Oo%Sru zNLV1Jv=Dh}4a`%bL)ZgQq2S5>q+!V>~K^Uuipp*vy+u>T}WLDbt8^9-1!)GWfobSY%|cQy`j5! zt6X|QyU4TGpDUfk844`2)-=#SWMMbV;#99avF3dan$;`Dg*=cfW`D&%m4q3vj&Ryc z<7C}dRyn&rXzMqrsGp5oUDEnuP-|J7thm-WmuMdSxshg)6=gIRr*Q@|WQq-S6EqGYok7*A)N&Uz-m1 literal 0 HcmV?d00001 diff --git a/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-2.geojson.gz b/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-2.geojson.gz new file mode 100644 index 0000000000000000000000000000000000000000..c803e43d49ac105c11cd585cd8b42dc07550fec5 GIT binary patch literal 362 zcmV-w0hRtAiwFRmj(cJN1GSdRO2a@DhWB}jkaa2NHs|6~xR)+OiwM$)c2mT6cM7%L zn2bXvn;DoBKIT7{Kl9b~{ct?J?}xj7xF6q6FXLg@oDZMI@#FdR@Oa#R=Ga3%>M!eU z?|U@chN`5b?(C3sbB)o~B}H;KP%*SiV7IBKs3Qcq275?}lL7#loFr@|jsPlMI>YemZ+MaKOk-*cV@6aXOw|i+#@kzQ+PrW7pme|MuKVtO09A~k IQl|?503m*_NdN!< literal 0 HcmV?d00001 diff --git a/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-3.geojson.gz b/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/lucene-10563-3.geojson.gz new file mode 100644 index 0000000000000000000000000000000000000000..81ba08f36f6075298596773f1521ff6ab05c1e6d GIT binary patch literal 358 zcmV-s0h#_EiwFRvj(cJN1GSdHN(3Cdn5#7rozPl0D zU17CjozA6!2EO)B|AfAEeLw8>&)ea$AFjs7{r&hftUfny$MNOv;pt|#eU038Z1sor z6#E`2ubtgfB0x-_wmRl65Z6Qsp0lw>kV9&-`VS{5bEa&{M3bN_!jzJbF9`AkfJPj- zS;`u5j7=oexe3qF)r_M#5-1UHy3k2AbDS%<97wYeF>r(_j8V>#!YB+&Vd`XoyJc0^ zy%{H=IVeWZ363BXbhYBhX|_&1eE8$|@XLW} ztQZAu)AL2p-