You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by iv...@apache.org on 2026/02/02 17:06:37 UTC

(lucene) branch branch_10x updated: Fix tessellator failure by preferring the shared vertex that is the leftmost vertex of the hole (#15657)

This is an automated email from the ASF dual-hosted git repository.

ivera pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new e59cb5bca5d Fix tessellator failure by preferring the shared vertex that is the leftmost vertex of the hole (#15657)
e59cb5bca5d is described below

commit e59cb5bca5dcb1653355eb028de767af8ae7f322
Author: Ignacio Vera <iv...@apache.org>
AuthorDate: Mon Feb 2 17:05:33 2026 +0000

    Fix tessellator failure by preferring the shared vertex that is the leftmost vertex of the hole (#15657)
---
 lucene/CHANGES.txt                                 |   3 +++
 .../java/org/apache/lucene/geo/Tessellator.java    |  26 +++++++++++++++++++--
 .../org/apache/lucene/geo/TestTessellator.java     |   6 +++++
 .../lucene/tests/geo/github-15657.geojson.gz       | Bin 0 -> 1394 bytes
 4 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index fa7f9acb523..9567cfd39de 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -203,6 +203,9 @@ Bug Fixes
 
 * GITHUB#14616 : Fix mis-interpretation of `maxNumBytes` in FuzzySet#createSetBasedOnMaxMemory (Greg Miller)
 
+* GITHUB#15554: Fix tessellator failure by preferring the shared vertex that is the leftmost vertex of the hole
+  (Ignacio Vera)
+
 Other
 ---------------------
 * GITHUB#15237: Fix SmartChinese to only deserialize dictionary data from classpath with a native array
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 697bcdce5ba..03fca6b005e 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Tessellator.java
@@ -408,18 +408,31 @@ public final class Tessellator {
     // Attempt to find a common point between the HoleNode and OuterNode.
     Node sharedVertex = null;
     Node sharedVertexConnection = null;
+    // Track the leftmost vertex match (holeNode is the leftmost vertex of the hole)
+    Node leftmostSharedVertex = null;
+    Node leftmostSharedVertexConnection = null;
     Node next = outerNode;
     do {
       if (Rectangle.containsPoint(
           next.getY(), next.getX(), holeMinY, holeMaxY, holeMinX, holeMaxX)) {
         Node newSharedVertex = getSharedVertex(holeNode, next);
         if (newSharedVertex != null) {
+          // Check if this shared vertex is the leftmost point of the hole (holeNode)
+          if (isVertexEquals(newSharedVertex, holeNode)) {
+            if (leftmostSharedVertex == null) {
+              leftmostSharedVertex = newSharedVertex;
+              leftmostSharedVertexConnection = next;
+            } else if (newSharedVertex.equals(leftmostSharedVertex)) {
+              // Same vertex found again via different connection
+              leftmostSharedVertexConnection =
+                  getSharedInsideVertex(leftmostSharedVertex, leftmostSharedVertexConnection, next);
+            }
+          }
           if (sharedVertex == null) {
             sharedVertex = newSharedVertex;
             sharedVertexConnection = next;
           } else if (newSharedVertex.equals(sharedVertex)) {
-            // This can only happen if this vertex has been already used for a bridge. We need to
-            // choose the right one.
+            // Same vertex found again via different connection.
             sharedVertexConnection =
                 getSharedInsideVertex(sharedVertex, sharedVertexConnection, next);
           }
@@ -427,6 +440,15 @@ public final class Tessellator {
       }
       next = next.next;
     } while (next != outerNode);
+
+    // The leftmost vertex of the hole is a shared vertex. Prefer this connection point if it is
+    // from a hole that was already merged (higher idx), as this maintains proper connectivity
+    // for chained holes.
+    if (leftmostSharedVertex != null
+        && leftmostSharedVertexConnection.idx > sharedVertexConnection.idx) {
+      splitPolygon(leftmostSharedVertexConnection, leftmostSharedVertex, true);
+      return true;
+    }
     if (sharedVertex != null) {
       // Split the resulting polygon.
       splitPolygon(sharedVertexConnection, sharedVertex, true);
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 b24dee3485f..33e8480dcdf 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestTessellator.java
@@ -935,6 +935,12 @@ public class TestTessellator extends LuceneTestCase {
     checkMultiPolygon(polygons, 1e-11);
   }
 
+  public void testComplexPolygon62() throws Exception {
+    String geoJson = GeoTestUtil.readShape("github-15657.geojson.gz");
+    Polygon[] polygons = Polygon.fromGeoJSON(geoJson);
+    checkMultiPolygon(polygons, 1e-11);
+  }
+
   private static class TestCountingMonitor implements Tessellator.Monitor {
     private int count = 0;
     private int splitsStarted = 0;
diff --git a/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/github-15657.geojson.gz b/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/github-15657.geojson.gz
new file mode 100644
index 00000000000..399f71063b0
Binary files /dev/null and b/lucene/test-framework/src/resources/org/apache/lucene/tests/geo/github-15657.geojson.gz differ