LUCENE-7215: don't invoke full haversin for LatLonPoint.newDistanceQuery

This commit is contained in:
Robert Muir 2016-04-13 19:02:11 -04:00
parent 1a1c8dbfb3
commit 2335a458d8
3 changed files with 52 additions and 25 deletions

View File

@ -39,7 +39,8 @@ Optimizations
* LUCENE-7071: Reduce bytes copying in OfflineSorter, giving ~10% * LUCENE-7071: Reduce bytes copying in OfflineSorter, giving ~10%
speedup on merging 2D LatLonPoint values (Mike McCandless) speedup on merging 2D LatLonPoint values (Mike McCandless)
* LUCENE-7105: Optimize LatLonPoint's newDistanceQuery. (Robert Muir) * LUCENE-7105, LUCENE-7215: Optimize LatLonPoint's newDistanceQuery.
(Robert Muir)
* LUCENE-7109: LatLonPoint's newPolygonQuery supports two-phase * LUCENE-7109: LatLonPoint's newPolygonQuery supports two-phase
iteration. (Robert Muir) iteration. (Robert Muir)

View File

@ -150,6 +150,10 @@ public class TestSloppyMath extends LuceneTestCase {
} }
} }
public void testHaversinFromSortKey() {
assertEquals(0.0, haversinMeters(0), 0.0D);
}
public void testAgainstSlowVersion() { public void testAgainstSlowVersion() {
for (int i = 0; i < 100_000; i++) { for (int i = 0; i < 100_000; i++) {
double lat1 = GeoTestUtil.nextLatitude(); double lat1 = GeoTestUtil.nextLatitude();

View File

@ -97,15 +97,8 @@ final class LatLonPointDistanceQuery extends Query {
NumericUtils.intToSortableBytes(Integer.MAX_VALUE, minLon2, 0); NumericUtils.intToSortableBytes(Integer.MAX_VALUE, minLon2, 0);
} }
// compute a maximum partial haversin: unless our box is crazy, we can use this bound // compute exact sort key: avoid any asin() computations
// to reject edge cases faster in visit() final double sortKey = sortKey(radiusMeters);
final double maxPartialDistance;
if (box.maxLon - longitude < 90 && longitude - box.minLon < 90) {
maxPartialDistance = Math.max(SloppyMath.haversinSortKey(latitude, longitude, latitude, box.maxLon),
SloppyMath.haversinSortKey(latitude, longitude, box.maxLat, longitude));
} else {
maxPartialDistance = Double.POSITIVE_INFINITY;
}
final double axisLat = Rectangle.axisLat(latitude, radiusMeters); final double axisLat = Rectangle.axisLat(latitude, radiusMeters);
@ -160,13 +153,9 @@ final class LatLonPointDistanceQuery extends Query {
double docLatitude = decodeLatitude(packedValue, 0); double docLatitude = decodeLatitude(packedValue, 0);
double docLongitude = decodeLongitude(packedValue, Integer.BYTES); double docLongitude = decodeLongitude(packedValue, Integer.BYTES);
// first check the partial distance, if its more than that, it can't be <= radiusMeters // its a match only if its sortKey <= our sortKey
double h1 = SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude); if (SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude) <= sortKey) {
if (h1 <= maxPartialDistance) { result.add(docID);
// fully confirm with part 2:
if (SloppyMath.haversinMeters(h1) <= radiusMeters) {
result.add(docID);
}
} }
} }
@ -197,20 +186,20 @@ final class LatLonPointDistanceQuery extends Query {
if ((longitude < lonMin || longitude > lonMax) && (axisLat+ Rectangle.AXISLAT_ERROR < latMin || axisLat- Rectangle.AXISLAT_ERROR > latMax)) { if ((longitude < lonMin || longitude > lonMax) && (axisLat+ Rectangle.AXISLAT_ERROR < latMin || axisLat- Rectangle.AXISLAT_ERROR > latMax)) {
// circle not fully inside / crossing axis // circle not fully inside / crossing axis
if (SloppyMath.haversinMeters(latitude, longitude, latMin, lonMin) > radiusMeters && if (SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMin) > sortKey &&
SloppyMath.haversinMeters(latitude, longitude, latMin, lonMax) > radiusMeters && SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMax) > sortKey &&
SloppyMath.haversinMeters(latitude, longitude, latMax, lonMin) > radiusMeters && SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMin) > sortKey &&
SloppyMath.haversinMeters(latitude, longitude, latMax, lonMax) > radiusMeters) { SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMax) > sortKey) {
// no points inside // no points inside
return Relation.CELL_OUTSIDE_QUERY; return Relation.CELL_OUTSIDE_QUERY;
} }
} }
if (lonMax - longitude < 90 && longitude - lonMin < 90 && if (lonMax - longitude < 90 && longitude - lonMin < 90 &&
SloppyMath.haversinMeters(latitude, longitude, latMin, lonMin) <= radiusMeters && SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMin) <= sortKey &&
SloppyMath.haversinMeters(latitude, longitude, latMin, lonMax) <= radiusMeters && SloppyMath.haversinSortKey(latitude, longitude, latMin, lonMax) <= sortKey &&
SloppyMath.haversinMeters(latitude, longitude, latMax, lonMin) <= radiusMeters && SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMin) <= sortKey &&
SloppyMath.haversinMeters(latitude, longitude, latMax, lonMax) <= radiusMeters) { SloppyMath.haversinSortKey(latitude, longitude, latMax, lonMax) <= sortKey) {
// we are fully enclosed, collect everything within this subtree // we are fully enclosed, collect everything within this subtree
return Relation.CELL_INSIDE_QUERY; return Relation.CELL_INSIDE_QUERY;
} else { } else {
@ -230,6 +219,39 @@ final class LatLonPointDistanceQuery extends Query {
}; };
} }
/**
* binary search to find the exact sortKey needed to match the specified radius
* any sort key <= this is a query match.
*/
static double sortKey(double radius) {
// effectively infinite
if (radius >= SloppyMath.haversinMeters(Double.MAX_VALUE)) {
return SloppyMath.haversinMeters(Double.MAX_VALUE);
}
// this is a search through non-negative long space only
long lo = 0;
long hi = Double.doubleToRawLongBits(Double.MAX_VALUE);
while (lo <= hi) {
long mid = (lo + hi) >>> 1;
double sortKey = Double.longBitsToDouble(mid);
double midRadius = SloppyMath.haversinMeters(sortKey);
if (midRadius == radius) {
return sortKey;
} else if (midRadius > radius) {
hi = mid - 1;
} else {
lo = mid + 1;
}
}
// not found: this is because a user can supply an arbitrary radius, one that we will never
// calculate exactly via our haversin method.
double ceil = Double.longBitsToDouble(lo);
assert SloppyMath.haversinMeters(ceil) > radius;
return ceil;
}
public String getField() { public String getField() {
return field; return field;
} }