All findings · Lucene finding · LFV-274 · geo3d-outside-distance-small-circle-ellipsoid-infinity

On WGS84, the geo3d outside distance to a small circle is +Infinity in most directions, and newOutsideDistanceSort sorts those documents last

On the default WGS84 planet model, computeOutsideDistance for a 1 km geo3d circle returns +Infinity for 10 of 12 points 3 km away instead of about 2,000 m, and Geo3DDocValuesField.newOutsideDistanceSort sorts such documents last, as if they had no location. Every release since 5.3.0.

Summary

Priority
Low, rank 409Geo3d outside distance and newOutsideDistanceSort silently return +Infinity for small circles on WGS84, which affects only sort and distance values in a little-used module, so it heads the new geo rows in Low.
Impact
silent wrong results
Since
5.3.0 (LUCENE-6578, 2015); the sort since 6.1.0introduced 2015-07-01 · over 11 years
Affected
5.3.0 to 10.5.2 and main (15 releases executed: the geometry on 13, the sort on 10)
Status
documented
Found by
testing of the geo3d distance sorts

AI-assisted page: this finding was first signalled by code reading or testing and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

geo3d finds a point's outside distance to a circle by searching one line in the circle's plane for the nearest edge point. That line always passes through a point that is the circle's centre on a sphere but 21 km north of it on WGS84 (at latitude 40), so it misses a small circle.

Summary diagram. Left: a 1 km circle at 40°N 74°W on WGS84 with 12 points 3 km from the centre; due north and due south get 2,000.0 m, the other 10 get +Infinity; expected about 2,000 m at every bearing. Middle: Plane.arcDistance searches on a line that always runs through F, the point of the circle's plane nearest the origin; on a sphere F is the circle's centre, on WGS84 at 40° it is 21,078 m north of it, so the lines miss the 1 km circle and the start value +Infinity is returned. Right: the sort gives most documents +Infinity, which ties them with documents that have no location; reach by circle size; the sampled points at the equator and on the sphere are correct; the fix searches on a line through the circle's centre.
The 1 km circle with its 12 points (2 right, 10 +Infinity), the search line through F 21 km north of the centre, and the reach and fix. Select the still to open it at full size. Generated with AI, checked against the code.
  1. Build a 1 km circle at 40°N 74°W on PlanetModel.WGS84 and ask computeOutsideDistance(DistanceStyle.ARC, p) for 12 points 3 km from the centre, one every 30°.
  2. You expect about 2,000 m in every direction (the distance to the edge), as on PlanetModel.SPHERE, where all 12 are 2,000 m.
  3. Instead due north and due south give 2,000 m and the other 10 points give +Infinity, with no error. newOutsideDistanceSort gives 10 of 12 documents 20 km outside the circle the sort value +Infinity, the value reserved for documents without a location, so they sort last.
  4. Why: Plane.arcDistance looks for the nearest edge point on a line through F, the point of the circle's plane nearest the planet's centre. On a sphere F is the centre of the circle's ring, on the radius through the circle's centre. On WGS84 the plane's normal follows the surface normal, 0.19° off the centre direction at latitude 40, so F lies 21 km north of the centre and the lines toward most points miss a 1 km circle.
  5. The smaller the circle, the more directions fail: 28 of 36 at 1 km, 8 of 36 at 20 km (where finite values are up to 188% off), none at 100 km (2.3% off). On the sphere and at the equator the sampled points are correct.

Who is affected

Callers of GeoCircle.computeOutsideDistance (ARC or LINEAR) and applications sorting with newOutsideDistanceSort on WGS84 or another non-spherical model, with a radius smaller than F's latitude-dependent offset (up to 21 km at mid latitudes); larger circles get finite but wrong values. Which documents match is not affected. spatial3d is opt-in, and this API is rarely used.

Mechanism, terms and numbers977 words

GeoCircle.computeOutsideDistance (ARC or LINEAR style) asks the circle's plane for the distance from a point to the circle's edge, Plane.arcDistance or Plane.linearDistance. Those methods look for the nearest edge point on one line: where the circle's plane meets the plane through the origin, the plane's normal and the point. On a sphere that line always passes through the centre of the circle's ring (on the radius through the circle's centre), so it always crosses the edge. On WGS84, or any non-spherical model, the circle plane's normal follows the surface normal, tilted 0.19° from the centre direction at latitude 40, so the line always runs through a point 21 km north of the centre. For a circle smaller than that offset the line misses the edge in most directions, the intersection search finds no point, and the method returns its start value, +Infinity: for 12 points 3 km from a 1 km circle at 40°N 74°W, one every 30°, only due north and due south are finite, the other 10 are +Infinity. At 5° steps 56 of 72 are +Infinity; within about 15° of north and 20° of south the line still crosses the circle, and the values are finite but wrong (up to 2,430 m where about 2,000 m is expected). Geo3DDocValuesField.newOutsideDistanceSort uses this distance, so 10 of 12 documents 21 km from the centre get +Infinity, the value its javadoc reserves for documents without the field, and sort last. Larger circles get finite but wrong values (at latitude 40, points 2 km outside: up to 9.9% off at 50 km, 2.3% at 100 km, 0.6% at 200 km). Which documents match is not affected. The code dates from LUCENE-6578 (5.3.0, 2015; the wrong value is Double.MAX_VALUE before 6.1.0); the sort is affected since it was added in 6.1.0.

Terms

  • Outside distance: GeoOutsideDistance.computeOutsideDistance(style, point) is 0.0 inside a shape and "the distance from a point to the closest outside edge" outside it. DistanceStyle.ARC measures the angle, LINEAR the straight-line (chord) distance, both in planet units (multiply by the mean radius for meters). ARC is the angle at the planet's centre, not a geodesic surface distance.
  • Circle plane: a geo3d circle (GeoStandardCircle, from GeoCircleFactory.makeGeoCircle) is the part of the planet surface on one side of a plane; its edge is where the plane cuts the surface. On WGS84 that cut is a slightly elliptical ring.
  • F: the point of the circle plane nearest the planet's centre. It lies on the plane's normal through the origin.

The bug

  1. GeoStandardCircle.outsideDistance is one call: distanceStyle.computeDistance(planetModel, circlePlane, x, y, z) (GeoStandardCircle.java:168-172), which ArcDistance and LinearDistance forward to Plane.arcDistance / Plane.linearDistance (ArcDistance.java:52-60, LinearDistance.java:52-60).
  2. Plane.arcDistance (Plane.java:600-636) builds perpPlane = new Plane(this.y * z - this.z * y, this.z * x - this.x * z, this.x * y - this.y * x, 0.0) (:612-614), the plane through the origin, the normal n and the point p; intersects it with the circle plane on the surface (findIntersections, :622); starts from double minDistance = Double.POSITIVE_INFINITY; (:625); and returns the minimum over the intersection points (:627-635). linearDistance is the same (:727-761).
  3. The two planes meet in a line through F. On a sphere F is the centre of the circle's ring, on the radius through the circle's centre, so every such line crosses the circle.
  4. On WGS84 the circle plane goes through the two meridian points at latitude ± the radius (GeoStandardCircle.java:71-102), so its normal follows the surface normal. At latitude 40 the normal is tilted 0.19° and F is 21,078 m north of the centre (7.3 km at latitude 10, 13.7 km at 70, 0.7 km at 89, 0 at the equator).
  5. Due north and south the point lies in the meridian plane, which holds n, F and the centre, so the line crosses the circle and the answer is right. At the other ten 30° bearings the line from F toward a point 3 km from the centre misses a 1 km circle, findIntersections returns no point, and +Infinity is returned. Bearings within about 15° of north or 20° of south still cross the circle, at the wrong points (finite, up to 2,430 m where about 2,000 m is expected, at 5° steps).
  6. A circle larger than the offset contains F, so the line always crosses it, but at the wrong points: finite, wrong values.
  7. Geo3DDocValuesField.newOutsideDistanceSort builds the circle with Geo3DUtil.fromDistance (Geo3DDocValuesField.java:249-258), and its comparator calls computeOutsideDistance(DistanceStyle.ARC, ...) per doc value (Geo3DPointOutsideDistanceComparator.java:101, :143-147) and returns it times the mean radius (:125). +Infinity stays +Infinity.
  8. GeoExactCircle uses the same Plane.arcDistance per slice (GeoExactCircle.java:245-258): small exact circles fail the same way. Its remaining +Infinity at the slice joints due north and/or due south (at most radii tested; on stock the 1 km exact circle's due-north value is finite, 1,998.6 m) is a separate bug (LFV-737).

The numbers

ARC distances of points 2 km outside the circle, 36 bearings, against a reference edge from bisecting the circle's own isWithin along 1,440 bearings (a high-resolution sampled reference; main a9779ef3c193):

circle radiusWGS84 +InfinityWGS84 worst finite valueSPHERE
1 km28 of 3621% off0 of 36, under 0.01% off
5 km18 of 3681% off0 of 36, under 0.01% off
20 km8 of 36188% off0 of 36, under 0.01% off
100 km0 of 362.3% off0 of 36, under 0.01% off

CLARKE_1866 is the same (28, 18, 8, 0). 1 km circle, 72 bearings: 56 of 72 +Infinity at latitudes 10, 40 and 70; none at the equator or at latitude 89 (up to 12% off there). These are sampled points: the antipode of the circle's centre gives +Infinity on the sphere too (a degenerate search plane, see Suggested fix).

Exposure and likelihood282 words
  • Who is hit. Code that calls GeoCircle.computeOutsideDistance with ARC or LINEAR (LINEAR_SQUARED squares the same linearDistance, read) on WGS84, CLARKE_1866 or any non-spherical PlanetModel, and applications that sort with Geo3DDocValuesField.newOutsideDistanceSort on such a model. In the sampled cases +Infinity appears when the radius is smaller than F's offset (0 at the equator, 7.3 km at latitude 10, 21 km at 40, 13.7 km at 70). The Geo3DPoint constructors without a PlanetModel argument use WGS84; newOutsideDistanceSort takes the model as an argument since 8.5.0 and used WGS84 unconditionally in 6.1.0-8.4.x. Small GeoExactCircles too.
  • Effect. Silent: +Infinity, or a wrong finite value, and no error. In a sort, documents a few km away rank after farther ones due north or south, tied with documents that have no location; with a small page size they fall off the first page.
  • Not affected. Which documents match: Geo3DPoint.newDistanceQuery decides membership with isWithin, and in an executed probe a filter on the same circle returned exactly the inside documents while adding the sort left the hit set unchanged. Nothing is written to the index; it is query-time only, for any index size. On PlanetModel.SPHERE and at the equator the sampled GeoStandardCircle points are correct (at the equator at most 0.2% off, the same as with the fix; GeoExactCircle keeps its separate joint +Infinity there, LFV-737); that is sampled evidence, not a proof.
  • Scale. No special data: one circle and one point.
  • How common. spatial3d is an opt-in module and outside-distance sorts are a rarely used API: no upstream test calls newOutsideDistanceSort, and a read-only code search found no use in Elasticsearch, OpenSearch or Solr. No report of it was found (see Prior reports).

Minimal repro

The test shows that on WGS84, GeoCircle.computeOutsideDistance(ARC) from a point 3 km due east of the centre of a 1 km circle (40N 74W) is +Infinity.

TestGeoCircleOutsideDistanceWGS84.java (JUnit test)32 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.spatial3d.geom;

import org.apache.lucene.tests.util.LuceneTestCase;

public class TestGeoCircleOutsideDistanceWGS84 extends LuceneTestCase {

  public void testOutsideDistanceToSmallCircle() {
    PlanetModel pm = PlanetModel.WGS84;
    double lat = Math.toRadians(40), lon = Math.toRadians(-74);
    GeoCircle circle = GeoCircleFactory.makeGeoCircle(pm, lat, lon, 1000 / pm.getMeanRadius());
    GeoPoint east = new GeoPoint(pm, lat, lon + 3000 / (pm.getMeanRadius() * Math.cos(lat)));
    double meters = circle.computeOutsideDistance(DistanceStyle.ARC, east) * pm.getMeanRadius();
    // expected: ~2000 m from a point 3 km east of the centre to the 1 km circle; actual: Infinity
    assertEquals(2000, meters, 100);
  }
}
Command
# in a copy of this finding's repro folder (repro/LFV-274/, which holds src/)
# 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:$LUCENE/lucene/spatial3d/build/libs/lucene-spatial3d-$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.spatial3d.geom.TestGeoCircleOutsideDistanceWGS84
Expected vs actual
Expected: about 2000 m (the distance to the circle edge; the fixes give 1998.03 m); actual: expected:<2000.0> but was:<Infinity>.

All files of this repro: repro/LFV-274/

More files, environment, full output34 words

Environment

luceneLucene main e1faeba08bde (2026-11-02)
jdk25.0.3+9-LTS

Observed output

# Lucene main 645d4fe14a20, assertions on (-ea), seed DEADBEEF: 1 test(s) run, 1 failed
    => java.lang.AssertionError: expected:<2000.0> but was:<Infinity>
       org.junit.Assert.fail(Assert.java:89)
       org.junit.Assert.failNotEquals(Assert.java:835)
       org.junit.Assert.assertEquals(Assert.java:555)
       org.junit.Assert.assertEquals(Assert.java:685)
       org.apache.lucene.spatial3d.geom.TestGeoCircleOutsideDistanceWGS84.testOutsideDistanceToSmallCircle(TestGeoCircleOutsideDistanceWGS84.java:30)

Why it looks unintended

  • Contract: "the distance from a point to the closest outside edge"; the sort reserves +Infinity for documents missing the field.
  • Plane.linearDistance is the distance to "the nearest intersection of this plane with the planet surface"; that intersection exists.
  • Sampled sphere, equator and 100-2,000 km WGS84 circles: within 2.3%; no test checks outside distance off the sphere.
The full argument344 words
  • GeoOutsideDistance (GeoOutsideDistance.java:20-21): "Implemented by Geo3D shapes that can compute the distance from a point to the closest outside edge."; :32-33: "A return value of 0.0 should be returned for points inside of the shape."
  • Plane.linearDistance (Plane.java:703-704, :717-718): "This is defined as the distance from the given point to the nearest intersection of this plane with the planet surface." The circle's boundary points lie on the plane (max |evaluate| 1.0e-12), yet it returns Infinity at 6 of 8 sampled bearings (executed on a9779ef3c193).
  • Geo3DDocValuesField.newOutsideDistanceSort (:232-237, not marked experimental): "This sort orders documents by ascending outside distance from the circle. Points within the circle have distance 0.0. The value returned in FieldDoc for the hits contains a Double instance with the distance in meters." and "If a document is missing the field, then by default it is treated as having Double#POSITIVE_INFINITY distance (missing values sort last)."
  • Offered, approximately, on ellipsoids. The circle factory and the sort take any PlanetModel, and the sampled WGS84 circles of 100 km to 2,000 km and the sampled equatorial circles come out within 2.3%: approximate, not undefined. In #8955 (2017-07-25) the geo3d maintainer wrote: "arcDistance is defined well for ellipsoids but surfaceDistance requires an iterative convergent loop. [...] Since surfaceDistance is prohibitively expensive to calculate for all but a sphere, an approximation is required either way, and the GeoCircle is fitted specially to the ellipsoid in order to be a good approximation for WGS84." That contrasts the arc-distance metric with true surface distance, not Plane.arcDistance's edge search, but it calls the WGS84 circle a good approximation, which +Infinity is not.
  • Tests. The only circle outside-distance asserts are on PlanetModel.SPHERE with a circle on the equator (TestGeoCircle.java:67-79), where the offset is 0; no test calls newOutsideDistanceSort. On #8267 (the sort's feature issue, still open) the last maintainer comment lists tests as still needed.
  • History. Plane.arcDistance has kept the plane through the origin since LUCENE-6578 (2015); later commits changed only the sentinel (LUCENE-7288: Double.MAX_VALUE to +Infinity) and the formatting.
The devil's-advocate check404 words

The strongest case for "intended": the geom API is @lucene.experimental; the circle's constructor says "In an ellipsoidal world, cutoff distances make no sense, unfortunately. Only membership can be used to make in/out determination." (GeoStandardCircle.java:66-67); the factory calls the shape "a circle in spherical planets, otherwise ... an ellipse"; open #9623 says the standard circle "is only accurate when the PlanetModel is a sphere"; in #9019 (2017) the maintainer wrote "GeoOutsideDistance we can't really do at the moment" about the exact circle; and, most directly, in #8955 (2017-07-21) a contributor proposed deciding whether a shape intersects a circle by comparing the shape's outside distance to the circle's centre with the radius, and the maintainer replied: "You are welcome to try to implement GeoArea for GeoCircles but remember that your distance computation does not work in WGS84, only in SPHERE." and "The 'GeoCircle' is by definition the intersection of a specific plane with the unit ellipsoid, so it's in fact an ellipse and distance calculations are exorbitantly expensive."; the contributor answered "Ok, I did not know that distance calculation is only support for unit sphere."

Why it fails: "experimental" allows API changes, not +Infinity for a point 2 km away, and the user-facing sort is not experimental. The constructor comment came with LUCENE-6487 (560f3a1a010, 2015), before outside distance existed, next to deleted membership-cutoff fields: it is about membership. #9623 is about small membership errors, and its remedy, GeoExactCircle, fails here too. The #9019 remark preceded the exact circle's outside distance, committed two days later, and concerns another shape. The #8955 exchange is about using the distance from the circle's centre as an intersection test, which cannot work on an ellipse because its edge is not at one distance from the centre; it does not say that outside distances may be +Infinity, and four days later the same maintainer called the WGS84 circle "a good approximation". The contracts, the 100 km and equatorial results and the sort's WGS84 default (hard-coded in 6.1.0-8.4.x) show that outside distance is offered on WGS84. An ellipse still has a nearest edge point. The verifiers and the adjudication rejected the intended reading; the cross-model reviews (OpenAI GPT-5.6; the models could not run code, a separate run re-ran the repro, which failed) and the cross-model fact check of this doc rated it moderate and agreed with the root cause. Expect a maintainer to raise the #8955 remark.

Suggested fix

Recommended Search on the line through the centre of the plane's section (Plane.arcDistance / linearDistance)

Build the search plane in Plane.arcDistance and linearDistance through the centre of the ellipse where the plane cuts the planet, not through the origin (+37/-4 in Plane.java). The repro passes with assertions on and off and the whole spatial3d test task passes; no +Infinity is left for standard circles. A narrower GeoStandardCircle-only fix leaves GeoExactCircle broken.

All options and trade-offs3 options · 1,002 words

Two fixes were built and validated with gradle in a fresh scratch worktree at a9779ef3c193 (removed afterwards). Option B fixes the shared method; option A only the standard circle. Either way, add a CHANGES entry under Bug Fixes and WGS84 tests of computeOutsideDistance and newOutsideDistanceSort. The exact circle's slice-joint bug (LFV-737) needs its own fix.

Search on the line through the centre of the plane's section (Plane.arcDistance / linearDistance) (validated by the checks) (recommended)

Replace the plane through the origin with the plane that contains the normal n and the point p and passes through c, the centre of the ellipse in which the plane cuts the planet: the point of the plane where the surface normal is parallel to n, c = t * (xyScaling² A, xyScaling² B, zScaling² C) with t = -D / (n · M⁻¹ n). Its line in the plane goes through c, so it crosses the ellipse whenever the plane meets the surface, with one degenerate exception: when p - c is parallel to n, the normal n × (p - c) is zero, findIntersections finds no point and +Infinity remains. For the 1 km circle that is a single point on the far side of the planet, 19,973 km from the centre. The current code has the same kind of point where p is parallel to n: on WGS84 a point 21,084 m due north of the centre (the fix gives 20,083.8 m there), and on the sphere the antipode, which stays +Infinity with the fix too. A final patch should handle this case explicitly. The edge point on the line from c toward the point's projection is the nearest one when the section is a circle (on the sphere); on an ellipse it is an approximation (under 0.005% off in the probe). For D == 0 it returns the old plane unchanged.

private Plane perpendicularPlane(
    final PlanetModel planetModel, final double x, final double y, final double z) {
  if (D == 0.0) {
    return new Plane(
        this.y * z - this.z * y, this.z * x - this.x * z, this.x * y - this.y * x, 0.0);
  }
  final double xyScalingSquared = planetModel.xyScaling * planetModel.xyScaling;
  final double zScalingSquared = planetModel.zScaling * planetModel.zScaling;
  final double mx = this.x * xyScalingSquared;
  final double my = this.y * xyScalingSquared;
  final double mz = this.z * zScalingSquared;
  final double t = -D / (this.x * mx + this.y * my + this.z * mz);
  final double cx = mx * t, cy = my * t, cz = mz * t; // centre of the section
  final double px = x - cx, py = y - cy, pz = z - cz;
  final double a = this.y * pz - this.z * py; // n x (p - c)
  final double b = this.z * px - this.x * pz;
  final double c = this.x * py - this.y * px;
  return new Plane(a, b, c, -(a * cx + b * cy + c * cz));
}
// arcDistance and linearDistance: final Plane perpPlane = perpendicularPlane(planetModel, x, y, z);

Trade-offs

  • Plane is shared by every outside-distance shape. Planes through the origin (polygon edges, great circles, longitude planes) keep the old code path; latitude planes give the same plane mathematically (read). Only tilted planes with D != 0 change: circle planes, exact-circle slices and the offset planes of path segments. On 6,724 points around 1 km and 20 km wide WGS84 and SPHERE paths, path outside distances changed by at most 0.4 mm.
  • A few extra multiplications per call; no performance measurement. Not run on x86 or another JDK. The 3 @Nightly tests were not run: two are query tests (TestGeo3DPoint.java:601, :606) and one a shape-relationship test (TestRandomGeoShapeRelationship.java:188); none calls these methods.
  • Still open: handle the degenerate case; add targeted tests for the changed D != 0 users (path sides, exact-circle slices: only the module suite and the 6,724-point path probe cover them); say in the javadoc that the result is an approximation on an ellipsoid. Recommended because it is the only option tried that also fixes small GeoExactCircles and keeps the Plane.linearDistance contract for every tilted plane; the GeoStandardCircle-only option has the smaller blast radius.

What the checks validated

  • Fresh scratch worktree at a9779ef3c193, gradle, seed DEADBEEF: the repro passes 3 of 3 with assertions on and off (stock: 2 of 3 fail; again in the fact check's fresh extraction); the whole :lucene:spatial3d:test task passes (20 classes, 184 tests, 3 @Nightly skipped, 0 failures; stock with the repro: only its 2 tests fail); checkGoogleJavaFormat, ecjLintMain, ecjLintTest, forbiddenApisMain and forbiddenApisTest pass.
  • Shadowed onto the stock jars: the isWithin-bisection probe gives 0 +Infinity and under 0.005% error for WGS84, CLARKE_1866 and a flattening-3e-4 planet at 1, 5, 20 and 100 km; the latitude sweeps give 0 +Infinity for the standard circle, and for GeoExactCircle only the 2 of 72 joint bearings of LFV-737 remain (57 of 72 before); the independent 7-test check passes 6, the one failure being the exact circle at bearings 0 and 180 (LFV-737).

Fix only GeoStandardCircle.outsideDistance (validated by the checks)

For ARC, LINEAR and LINEAR_SQUARED, intersect circlePlane with the plane through the origin, the circle's centre and the point; its line crosses the inside of the circle. NORMAL styles unchanged; falls back to the old computation if no intersection is found (+28/-1 in GeoStandardCircle.java, the finder's sketch re-wrapped for google-java-format).

Trade-offs

Smallest change, but GeoExactCircle stays broken (the independent check still fails 11 of 12 bearings for it; the sweep still 57 of 72), and other tilted D != 0 planes keep the spherical assumption.

What the checks validated

Same worktree: the repro passes with assertions on and off; the whole spatial3d test task passes (184 tests); checkGoogleJavaFormat, ecjLintMain and forbiddenApisMain pass; the bisection probe and sweeps give the same standard-circle results as the recommended fix.

Document outside distances as sphere-only (not recommended)

Say in the javadoc that outside distances need PlanetModel.SPHERE.

Trade-offs

Contradicts the existing contracts and the correct results for large WGS84 circles, and leaves the sort's +Infinity in place.

Downstream note

Downstream: a code search found no use of these APIs in Elasticsearch, OpenSearch or Solr; an application that offers an outside-distance sort with small WGS84 circles would show most nearby results last, as if they had no location.

How it was found

Testing, not a formal method. While measuring the geo3d outside-distance sort for a related bug, the finder saw documents far outside a small circle come back +Infinity, reproduced it on exact surface points with the geometry API, and confirmed the missed intersection by calling findIntersections on the circle's own plane.

The method in full150 words

Method: testing in the hunt's spatial3d geometry unit (no model checker, solver or prover was involved).

  • While measuring the outside-distance sort for a related candidate (decoded doc values off the surface, LFV-247), the finder saw documents far outside a small WGS84 circle come back with +Infinity.
  • It isolated the effect on exact surface points with the geometry API: a bearing sweep (36 bearings) and a latitude × radius sweep (72 bearings at 5 latitudes and 4 radii).
  • It confirmed the mechanism by rebuilding the plane that Plane.arcDistance uses with the public API and calling findIntersections: 2 points due north, 0 at 30°, 60° and 90°, and F 21 km from the circle's centre (an independent reproducer's diagnostic later covered all 12 bearings).
  • It then wrote a public-API JUnit test on the stock jars and a narrow fix sketch; independent verifiers, cross-model reviews and an adjudication followed (see Independent checks).

Prior reports, history and checks

Prior reports

No prior report found (2026-10-26) in Lucene's GitHub issues and PRs, JIRA, the mailing lists, Solr, Elasticsearch, OpenSearch or the web; 25 hits read, none the same bug. Closest: #9623 (the standard circle is accurate only on a sphere, about membership) and #8267 (the sort's feature issue, tests still owed).

Searches6 searches

Verdict new: no report by anyone. Design context: #8955 (2017-07-21: "your distance computation does not work in WGS84, only in SPHERE", about a proposed intersection test; 2017-07-25: "arcDistance is defined well for ellipsoids but surfaceDistance requires an iterative convergent loop"; see Why it is not intended) and #9019 (2017: "GeoOutsideDistance we can't really do at the moment", about the exact circle, two days before its outside distance was committed). The prior-report check, a verifier and the adjudication read the #9019 remark as design-time doubt about another shape, not a report of this bug. No open PR touches the fix site (#14775 only adds inverseSort to the sort field). Gaps: list search is keyword based; private reports cannot be searched.

What was searched, when, and what turned up
WhereWhenHowFound
apache/lucene issues and PRs; dev@, java-user@, issues@ and commits@; JIRA archive; the web2026-10-26the hunt's prior-report tool: lists and JIRA 28 + 6 queries, GitHub 28 of 30 queries, web 6 (its commit and downstream queries hit GitHub's search rate limit and were replaced by the searches below)lists 474 hits and GitHub/web 566 hits, none high-overlap; every medium hit opened; none the same bug
GitHub: apache/lucene (all 134 module:spatial3d items), apache/lucene-solr, apache/solr, Elasticsearch, OpenSearch, all of GitHub2026-10-26148 manual GraphQL searchesno report
JIRA (LUCENE, SOLR) and lists (java-user@, dev@, issues@, solr-user@, users@solr, dev@solr)2026-10-2615 JIRA and 78 list queriesno report
Open PRs, issue threads, history2026-10-26the changed files of all 413 open apache/lucene PRs; 67 issues and PRs downloaded with all comments and searched for the bug's terms (5 read in full; in #9019, #9134 and #8955 the matching comments); git log -L / --grep and CHANGES.txt25 hits classified, none the same bug; no open PR touches the fix site
#8955 (88 comments) and #9019 (60 comments), the saved dumps2026-10-28re-read in full for this docno report of wrong or infinite outside distances; the #8955 remarks of 2017-07-21 and 2017-07-25 (quoted in Why it is not intended) and, in #9019, "Computing the distance from the plane to the center of the circle isn't going to work" (2017-09-23, about intersects() for the proposed exact circle)
Upstream main2026-10-28git log a9779ef3c193..origin/main -- lucene/spatial3d (head 20ced71c1e6e, 2026-10-27)no spatial3d change

History

Introduced with outside distances by LUCENE-6578 (688c67f8ff22, 2015-07-01), first released in 5.3.0; the sort is affected since it was added in 6.1.0. Fails on all 15 releases executed from 5.3.0 to 10.5.2 (the geometry on 13, the sort on 10) and on main.

Versions tested8 rows

Introduced by LUCENE-6578 (JIRA title "Geo3d: arcDistanceToShape() method may be useful"; commit message "Geo3D: compute the distance from a point to a shape"; JIRA patch, no pull request; GitHub #7636): trunk 688c67f8ff22, branch_5x 780e4040238, both 2015-07-01; patch by @DaddyWri, revised and committed by @dsmiley (CHANGES: "Karl Wright, David Smiley"). It added GeoOutsideDistance, DistanceStyle and Plane.arcDistance/linearDistance with the plane through the origin. The precondition, a circle plane through the meridian points (LUCENE-6544, #7602, 2015-06-18), also first shipped in 5.3.0. LUCENE-7288 (6.1.0) changed the no-result value from Double.MAX_VALUE to +Infinity; LUCENE-7212 (#8267, cc263413457, 6.1.0) added newOutsideDistanceSort; LUCENE-7970 (7.1.0) added GeoExactCircle, which reuses Plane.arcDistance. Not fixed upstream.

Versions and branches executed, with results
Version or commitVerdictResult
main a9779ef3c193 affectedrepro 2 of 3 fail, assertions on and off, through gradle and the standalone runner; the independent check 4 of 7
10.5.2 affectedrepro 2 of 3 fail; geometry 10 of 12 +Infinity, sort 10 of 12
6.1.0, 6.6.6, 7.7.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0 affectedgeometry 10 of 12 +Infinity; sort 10 of 12 +Infinity
8.4.1, 8.5.0 sort only runsort 10 of 12 +Infinity (before and after the PlanetModel argument)
5.3.0, 5.3.2, 5.4.0, 5.5.5, 6.0.0 Double.MAX_VALUEgeometry 10 of 12 return Double.MAX_VALUE (5.3.0 with assertions on and off); no sort API yet
5.2.1 API absentno outside-distance API (the repro does not compile)
fix in Plane, scratch worktree at a9779ef3c193 not affectedrepro 3 of 3 pass, on and off; the whole spatial3d test task passes
fix in GeoStandardCircle only, same worktree standard circle onlyrepro and the spatial3d test task pass; GeoExactCircle still 11 of 12 +Infinity

Independent checks

The finder's repro, an independent reproducer, a devil's-advocate check and an adjudication found it real; reviews by another model family (OpenAI GPT-5.6) agreed on the root cause. This doc re-ran it on main and 10.5.2, validated two fixes, and resolved three later checks of its text and diagrams.

Checks and verdicts511 words

The checks ran on main ea57360912ed unless stated; its spatial3d code is identical to a9779ef3c193.

Each independent check and its verdict
CheckVerdictWhat it found
finder candidatePublic-API JUnit fails 2 of 2 with assertions on and off and on 10.5.2, 9.12.3 and 9.4.2; passes with its narrow fix.
cross-model review (refute lens) not real (low), on procedure onlyOpenAI GPT-5.6, reading only: it could not run code, agreed with the root cause, found the inputs in contract and rated the intended-behaviour argument moderate; a separate re-run of the repro failed.
cross-model review (intended lens) real (medium)OpenAI GPT-5.6, reading only; a separate oracle check (executed) found 10 of 12 wrong for WGS84 and CLARKE_1866, 0 of 12 for SPHERE, a 200 km circle and the equator.
independent reproducer real (moderate to high)Own JUnit from the claim alone (isWithin-bisection oracle, separate geodetic points) fails 4 of 7 on main, on and off, two seeds, 10.5.2 and 9.12.3; controls pass; 0 intersection points at every +Infinity bearing.
devil's advocate realRe-ran the repro (fails, on and off); own probe over 1,440 bearings: 28 of 36 +Infinity at 1 km, 8 of 36 at 20 km with errors up to 188%; rebutted each intended reading.
adjudication confirmed (high)Re-ran the reproducer's test with a fresh seed (4 of 7 fail, on and off) and the finder's with assertions off; checked the mechanism against the code; silent wrong results, not security-sensitive.
history and security follow-ups 5.3.0 to main affected; not security-sensitive15 releases and main executed; the security probe showed the hit set unchanged, bounded cost and terminating searchAfter paging.
registry refresh still reproducesThe finder's and the reproducer's tests fail on a9779ef3c193 exactly as recorded, on and off.
this doc's runs reproduced; two fixes validatedNew repro 2 of 3 fail on main (gradle, on and off) and 10.5.2; both fixes pass it and the whole spatial3d test task; oracle probes and sweeps with each fix.
independent fact check of this doc real; 11 fixable errors, resolvedRe-ran the gradle repro verbatim on a fresh extraction (2 of 3 fail, assertions on and off; fix B 3 of 3), the harness runs on main and 10.5.2, a 5-degree bearing probe and a degenerate-point probe. Its corrections (5-degree bearings, the #8955 context, prior-art wording, release counts, the degenerate case) are in this doc.
cross-model fact check of this doc real; 7 errors, 7 overclaims, 6 caveats, resolvedOpenAI GPT-5.6, reading the code and the saved runs: the core bug and root cause hold; intended-behaviour strength moderate; asked for the degenerate case, sampled-point wording and the #8955 context.
diagram check 1 error (title card), fixed and re-renderedEvery value, API name and line reference matched the code and the probe except the title card's wording, now 'a line through a point 21,078 m from the centre'; four wording items were folded into the re-render.
resolution how each finding was handledAccepted, rejected and partly accepted findings with the reasons.

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,948 words

Finding record

LFV-274, snapshot of 2026-11-06

Registry title
On WGS84 and other non-spherical planet models, the outside distance from a point to a geo3d circle smaller than about 50 km is +Infinity in most directions (GeoCircle.computeOutsideDistance with the ARC or LINEAR style, and so Geo3DDocValuesField.newOutsideDistanceSort, which then sorts valid documents as if they had no value), because Plane.arcDistance and Plane.linearDistance look for the edge on the line where the circle's plane meets a plane through the origin, the plane normal and the point, and on an ellipsoid that line misses a small circle
Area
spatial3d / geom outside distance (Plane.arcDistance and Plane.linearDistance behind GeoStandardCircle and GeoExactCircle computeOutsideDistance; user-facing Geo3DDocValuesField.newOutsideDistanceSort via Geo3DPointOutsideDistanceComparator)
Priority
Low, rank 409: Geo3d outside distance and newOutsideDistanceSort silently return +Infinity for small circles on WGS84, which affects only sort and distance values in a little-used module, so it heads the new geo rows in Low.
Impact class
silent wrong results
Impact note
Applications that call GeoCircle.computeOutsideDistance (ARC or LINEAR style) or sort with Geo3DDocValuesField.newOutsideDistanceSort on the default WGS84 planet model, CLARKE_1866 or any other non-spherical model, with a circle smaller than about 50 km, get +Infinity instead of a distance for most points outside the circle, with no error: for a 1 km circle at latitude 40, points 2 km outside get +Infinity at 10 of 12 bearings (only due north and due south are right), and 11 of 12 documents 21 km from the centre sort with +Infinity, the value the javadoc reserves for documents missing the field; at latitudes 10-70 a 1 km circle gives 56 of 72 bearings +Infinity, a 20 km circle still 8 of 36 to 14 of 72 with finite values off by 36% to 188%, and circles of 50 km or more no +Infinity; the equator and PlanetModel.SPHERE are correct; any index size, query-time only (the hit set and Lucene's distance filter are not affected, nothing is persisted), every release since 5.3.0 (the sort since 6.1.0); GeoExactCircle, the documented accurate alternative, fails the same way.
Summary
You build a 1 km circle on the default WGS84 planet model (GeoCircleFactory.makeGeoCircle, or Geo3DDocValuesField.newOutsideDistanceSort with maxRadiusMeters 1000) and ask for the outside distance of points 3 km from the centre, expecting about 2 km in every direction (0.0 inside, distance to the edge outside). Instead computeOutsideDistance returns +Infinity at 10 of 12 bearings (only due north and due south are right), and the sort gives most documents outside the circle +Infinity, the value the javadoc reserves for documents missing the field, so they sort last. The cause: Plane.arcDistance and Plane.linearDistance look for the nearest edge point on the line where the circle's plane meets a plane through the origin, the plane normal and the point; on a sphere that line passes through the circle's centre, but on an ellipsoid the circle plane's normal is tilted, the line passes about 21 km from the circle at latitude 40, and for a smaller circle it finds no edge point at all, so the minimum keeps its +Infinity start value.
Introduced
688c67f8ff22b72b9df4ed2f92d26b909563b2b1 (lucene/dev/trunk svn r1688545, 2015-07-01, 'LUCENE-6578: Geo3D: compute the distance from a point to a shape.', patch by DaddyWri, committed by dsmiley; JIRA patch, no pull request; LUCENE-6578 is apache/lucene#7636) added GeoOutsideDistance, DistanceStyle, GeoCircle.outsideDistance and Plane.arcDistance/linearDistance with perpPlane = (n x p, D=0) and a minimum initialised to the sentinel; 5.3.0 shipped its branch_5x backport 780e4040238713c2ff85c4b981cbc6b80aa4094f (svn r1688546, same day); precondition LUCENE-6544 (#7602, trunk 03408382767770c8 / branch_5x 226f69dd68b, 2015-06-18, also first in 5.3.0) builds the circle's cut plane through the meridian points at latitude +/- radius, which tilts its normal on an ellipsoid; later: LUCENE-7288 (#8343, 2016-05-18, 6.1.0) changed the sentinel from Double.MAX_VALUE to +Infinity, LUCENE-7212 (#8267, cc263413457 / branch_6x aa339a2f075, 2016-05-18, 6.1.0) added newOutsideDistanceSort, LUCENE-7970 (#9019, 2c79710618e, 2017-09-25, 7.1.0) added GeoExactCircle, whose outsideDistance reuses Plane.arcDistance per slice; not a regression: present since the API was created [read: git log -L, git show and git grep in the full-history clone; exec: 5.2.1 has no outside-distance API (compile error), 5.3.0 fails] (2015-07-01 (LUCENE-6578: outside distance and Plane.arcDistance); 2016-05-18 (LUCENE-7212: Geo3DDocValuesField.newOutsideDistanceSort))
First affected version
5.3.0 [exec] (release tag 2015-08-20; the first release with GeoOutsideDistance and Plane.arcDistance; fails with -ea and -da: 10 of 12 bearings return the then-sentinel Double.MAX_VALUE, bearings 0 and 180 give 2000.0 m; the SPHERE and 500 km controls pass); 6.1.0 [exec] for the user-facing Geo3DDocValuesField.newOutsideDistanceSort (10 of 12 documents 21 km out sort with +Infinity); 5.2.1 and earlier have no outside-distance API [exec: compile error]
Affected versions
every release from 5.3.0 through 10.5.2 (the last 10.x release before 10.6.0, which was not checked; 9.12.3 is the latest 9.x) and main ea57360912ed. [exec] a version-portable geometry main() (WGS84 1 km circle at 40N 74W, 12 points 3 km out) fails with 10 of 12 bearings wrong on 5.3.0 (-ea and -da), 5.3.2, 5.4.0, 5.5.5, 6.0.0, 6.1.0, 6.6.6, 7.7.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and main (-ea and -da); an end-to-end sort main() (12 documents 21 km out) fails with 10 of 12 +Infinity on 6.1.0, 6.6.6, 7.7.3, 8.4.1, 8.5.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and main; the finder's JUnit fails on main (-ea, -da), 10.5.2, 9.12.3 and 9.4.2; the independent reproduction's JUnit on main (-ea, -da, two seeds), 10.5.2 and 9.12.3; the intended-behaviour check's probe on main (-ea, -da) and 10.5.2. [read] git log -L on Plane.arcDistance shows only LUCENE-6578, LUCENE-7288 (sentinel) and LUCENE-9570 (reformatting), and the cut-plane construction has gone through the meridian points since LUCENE-6544; unexecuted intermediate releases are inferred. Symptom value: Double.MAX_VALUE in 5.3.0-6.0.x, +Infinity from 6.1.0. GeoExactCircle (7.1.0 on) fails on main and 10.5.2 [exec]; its history was not executed. Not fixed on main; no open PR touches the fix site. All runs with JDK 25.0.3 on an aarch64 host.
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
non-formal
Upstream and prior reports
none found (2026-10-26, the prior-report record, verdict new): a scripted prior-report search over apache/lucene issues and PRs (28 of 30 queries ran), the dev@, java-user@, issues@ and commits@ lists (28), the JIRA archive (6) and the web (6); its 5 commit and 5 downstream queries did not run (GitHub's search rate limit) and were replaced by GraphQL searches and local git history; plus 148 manual GitHub GraphQL searches (apache/lucene issues and PRs including all 134 label:module:spatial3d items, apache/lucene-solr, apache/solr, elastic/elasticsearch, opensearch-project/OpenSearch and all of GitHub), 15 JIRA queries (LUCENE, SOLR), 78 list queries (java-user@, dev@, issues@, solr-user@, users@solr, dev@solr), the changed files of all 413 open apache/lucene PRs, 67 issues downloaded with all comments and searched, and git log -L / --grep and CHANGES.txt; 25 hits read and classified, none the same bug. Closest, not reports: apache/lucene#9623 (LUCENE-8577, open: GeoStandardCircle is only accurate on a sphere, about query membership; its proposed remedy GeoExactCircle fails the same way here), #8267 (LUCENE-7212, open: the feature issue of the outside-distance sort; the last maintainer comment lists tests as still needed), #7636 (LUCENE-6578, closed 2015: added Plane.arcDistance; ellipsoids and missed intersections never mentioned). Design context: #8955 (LUCENE-7906), where in 2017 the geo3d maintainer wrote 'arcDistance is defined well for ellipsoids', and #9019 (LUCENE-7970), where on 2017-09-23 they wrote 'GeoOutsideDistance we can't really do at the moment' for the not-yet-committed exact circle, whose outsideDistance was committed two days later. Not fixed upstream: upstream main is the tested commit, the Plane.arcDistance body is unchanged since 2015, and no open PR touches Plane, GeoStandardCircle, GeoExactCircle, ArcDistance/LinearDistance or the outside-distance comparator logic (#14775 only adds inverseSort to the sort field). Gaps: list search is keyword based; private security reports cannot be searched.
Lucene commit tested
main a9779ef3c193 (2026-10-26) with assertions on and off, through gradle in a fresh scratch worktree (and again by the fact check on a fresh source extraction, identical output) and through a standalone JUnit runner on the built jars; the 10.5.2 release jars; both fixes in the same worktree (the repro, the whole spatial3d test task, format and lint checks). Earlier runs: 15 releases from 5.3.0 to 10.5.2 (the geometry on 13, the sort on 10) and main ea57360912ed, whose spatial3d code is identical. Upstream main 20ced71c1e6e (2026-10-27) has no spatial3d change after a9779ef3c193 (read).

Lucene and upstream sources

Finding record
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/Plane.java:591-599 (at ea57360912ed) (arcDistance javadoc 'Compute arc distance from plane to a vector'), :600-636 (arcDistance: perpPlane = new Plane(n x p, 0.0) at :613-614, findIntersections(planetModel, perpPlane) at :622, minDistance = Double.POSITIVE_INFINITY at :625, returned unchanged when there is no intersection at :635), :703-705 and :717-719 (linearDistance javadoc: 'the distance from the given point to the nearest intersection of this plane with the planet surface'), :727-761 (linearDistance: same construction, findIntersections at :748), :24 (@lucene.experimental)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardCircle.java:66-67 (at ea57360912ed) (comment 'In an ellipsoidal world, cutoff distances make no sense, unfortunately. Only membership can be used to make in/out determination'), :71-104 (cut plane built through the upper and lower meridian points upperPoint/lowerPoint at :89-90, constructNormalizedPerpendicularSidedPlane at :101), :168-172 (outsideDistance = distanceStyle.computeDistance(planetModel, circlePlane, x, y, z), no bounds), :174-181 (isWithin = circlePlane.isWithin), :28 (@lucene.experimental)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoExactCircle.java:245-257 (at ea57360912ed) (outsideDistance: minimum of computeDistance(planetModel, slice.circlePlane, x, y, z, slice.plane1, slice.plane2) over the slices, start value +Infinity)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/ArcDistance.java:44-60 (at ea57360912ed) and geom/LinearDistance.java:44-60 (computeDistance(planetModel, plane, ...) delegates to plane.arcDistance / plane.linearDistance)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseMembershipShape.java:47-53 (at ea57360912ed) (computeOutsideDistance returns 0.0 if isWithin, else outsideDistance)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoOutsideDistance.java:19-23 (at ea57360912ed) ('compute the distance from a point to the closest outside edge', @lucene.experimental), :31-33 and :44-46 ('Compute this shape's distance to the GeoPoint. A return value of 0.0 should be returned for points inside of the shape')
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java:230-258 (at ea57360912ed) (newOutsideDistanceSort: 'orders documents by ascending outside distance from the circle. Points within the circle have distance 0.0', distance in meters, missing documents 'treated as having Double#POSITIVE_INFINITY distance (missing values sort last)' at :236-237; builds the circle with Geo3DUtil.fromDistance at :254-255; no @lucene.experimental tag in this class)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircleFactory.java:28-30 (at ea57360912ed) (makeGeoCircle: 'The resulting shape is a circle in spherical planets, otherwise is an ellipse'), :70 (makeExactGeoCircle)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointDistanceComparator.java:92-93, (at ea57360912ed) :155, :159-160 and Geo3DPointOutsideDistanceComparator.java:70-71, :125, :129-130 (setTopValue stores meters, value() returns meters, compareTop compares with planet units; the untriaged searchAfter side finding)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DUtil.java:152-162 (at ea57360912ed) (fromDistance: radiusMeters / getMeanRadius, GeoCircleFactory.makeGeoCircle)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideDistanceComparator.java:75-104 (at ea57360912ed) (compareBottom: computeOutsideDistance(DistanceStyle.ARC, ...) at :101), :125 (value() = values[slot] * getMeanRadius()), :133-152 (computeMinimumDistance at :143-147)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java:59 (at ea57360912ed) and :69 (default WGS84 PlanetModel), :113 (newDistanceQuery on WGS84 'is approximate and may have up to 0.5% error')
The finding in 30 seconds
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardCircle.java:66-67, 71-102, 168-181
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/Plane.java:591-636, 703-704, 716-761, 789-797, 1062-1200
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/ArcDistance.java:44-60; geom/LinearDistance.java:44-60; geom/GeoBaseMembershipShape.java:40-56; geom/GeoOutsideDistance.java:20-21, 32-33
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java:229-258; Geo3DUtil.java:152-162; Geo3DPointOutsideDistanceComparator.java:75-104, 123-152; geom/GeoExactCircle.java:245-258
Who is affected
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java:58-60, 112-113; geom/GeoStandardCircle.java:174-181; geom/Plane.java:789-797
Why it looks unintended
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoOutsideDistance.java:20-23, 32-33, 45-46; geom/Plane.java:24, 703-704, 717-718; Geo3DDocValuesField.java:232-237; geom/GeoStandardCircle.java:66-67; geom/GeoCircleFactory.java:28-29; geom/GeoCircle.java:22
  • a Lucene main checkout lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/TestGeoCircle.java:67-79
Suggested fix
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/Plane.java:600-636, 727-761; geom/GeoStandardPath.java:1784-1830 (segment offset planes); geom/PlanetModel.java:52-55, 142-149
  • a Lucene main checkout lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/Plane.java:53-55, 1070-1078 (zero normal accepted; NO_POINTS); lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java:601, 606; geom/TestRandomGeoShapeRelationship.java:188 (@Nightly)
Prior reports
  • a Lucene clone: git log a9779ef3c193..origin/main -- lucene/spatial3d (empty)

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

Made in parallel from executed probes on main a9779ef3c193 (the 12-point geometry, F's offset and tilt, the latitude and radius sweeps, a sort); the PNG is the final summary frame.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.