ShapeDocValues getCentroid() returns (0, 0), far outside the shape's own bounding box, when the shape's total encoded length or area is zero (a line whose points coincide after encoding, or a polygon thinner than one encoding step)

Finding LFV-366Area: document / shape doc values (ShapeDocValues centroid; LatLonShapeDocValuesField and XYShapeDocValuesField getCentroid)Priority: lowImpact: silent wrong resultsNovelty: new

Finding page for LFV-366

AI-assisted finding, confirmed by automated checks, not yet triaged by a human Lucene committer. This site files nothing upstream.

What it shows

You build a doc-values shape with LatLonShape.createDocValueField or XYShape.createDocValueField and ask for its centroid; you expect getCentroid(), documented as 'the centroid location for the geometry', to be a point of the shape, inside the bounding box that the same object returns. For a line whose points coincide after encoding (new Line({40, 40}, {-70, -70}), or two points closer than one encoding step) or a polygon thinner than one encoding step, Lucene returns Point(0.0, 0.0) instead, 7,500 km or more from the shape in these examples: the centroid is weighted by length or area, and when the total weight is zero the code skips the division and keeps the zero numerator. The other public factory, createDocValueField(name, createIndexableFields(name, line)), returns the real point for the same line.

The test shows that LatLonShape.createDocValueField("f", new Line(new double[] {40, 40}, new double[] {-70, -70})).getCentroid() returns (0, 0) for a line whose points coincide, because the length-weighted centroid skips the division when the total length is 0 (TestShapeDocValuesRepeatedPointCentroid#testCentroidOfLineWithRepeatedPoint).

Expected vs actual

Expected: (40, -70) within encoding error, the line's only point; actual: latitude expected:<40.0> but was:<0.0> (the centroid is (0, 0)), with -ea and -da.

Files

Run it

# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble   (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar   # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar   # JUnit Platform console launcher (Maven Central)

javac -d classes -cp "$CP" $(find src -name '*.java')

# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
     -cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
     execute --select-class org.apache.lucene.document.TestShapeDocValuesRepeatedPointCentroid

Gradle alternative: copy the test into the matching module's src/test/ tree of the checkout and run ./gradlew -p lucene/<module> test --tests <class> -Ptests.seed=<seed> (add -Ptests.asserts=false for a run without assertions).

Feedback

Open an issue in lucene-ai-bug-hunt. Please do not open issues or pull requests in apache/lucene about this finding unless a Lucene committer agrees it should be filed.

Snapshot of 2026-11-03. AI-assisted; not yet triaged by a human Lucene committer. Feedback: open an issue in lucene-ai-bug-hunt.