LUCENE-5118: multiplier to spatial makeDistanceValueSource

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1506632 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2013-07-24 17:20:10 +00:00
parent f3856fa67c
commit 47bf70812b
11 changed files with 89 additions and 28 deletions

View File

@ -64,6 +64,9 @@ New features
(default is false). If true then edits are measured in Unicode code (default is false). If true then edits are measured in Unicode code
points instead of UTF8 bytes. (Artem Lukanin via Mike McCandless) points instead of UTF8 bytes. (Artem Lukanin via Mike McCandless)
* LUCENE-5118: SpatialStrategy.makeDistanceValueSource() now has an optional
multiplier for scaling degrees to another unit. (David Smiley)
Bug Fixes Bug Fixes
* LUCENE-5116: IndexWriter.addIndexes(IndexReader...) should drop empty (or all * LUCENE-5116: IndexWriter.addIndexes(IndexReader...) should drop empty (or all

View File

@ -101,12 +101,21 @@ public abstract class SpatialStrategy {
*/ */
public abstract Field[] createIndexableFields(Shape shape); public abstract Field[] createIndexableFields(Shape shape);
/**
* See {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point, double)} called with
* a multiplier of 1.0 (i.e. units of degrees).
*/
public ValueSource makeDistanceValueSource(Point queryPoint) {
return makeDistanceValueSource(queryPoint, 1.0);
}
/** /**
* Make a ValueSource returning the distance between the center of the * Make a ValueSource returning the distance between the center of the
* indexed shape and {@code queryPoint}. If there are multiple indexed shapes * indexed shape and {@code queryPoint}. If there are multiple indexed shapes
* then the closest one is chosen. * then the closest one is chosen. The result is multiplied by {@code multiplier}, which
* conveniently is used to get the desired units.
*/ */
public abstract ValueSource makeDistanceValueSource(Point queryPoint); public abstract ValueSource makeDistanceValueSource(Point queryPoint, double multiplier);
/** /**
* Make a Query based principally on {@link org.apache.lucene.spatial.query.SpatialOperation} * Make a Query based principally on {@link org.apache.lucene.spatial.query.SpatialOperation}
@ -139,7 +148,7 @@ public abstract class SpatialStrategy {
/** /**
* Returns a ValueSource with values ranging from 1 to 0, depending inversely * Returns a ValueSource with values ranging from 1 to 0, depending inversely
* on the distance from {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point)}. * on the distance from {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point,double)}.
* The formula is {@code c/(d + c)} where 'd' is the distance and 'c' is * The formula is {@code c/(d + c)} where 'd' is the distance and 'c' is
* one tenth the distance to the farthest edge from the center. Thus the * one tenth the distance to the farthest edge from the center. Thus the
* scores will be 1 for indexed points at the center of the query shape and as * scores will be 1 for indexed points at the center of the query shape and as
@ -151,7 +160,7 @@ public abstract class SpatialStrategy {
ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY()); ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY());
double distToEdge = diagonalDist * 0.5; double distToEdge = diagonalDist * 0.5;
float c = (float)distToEdge * 0.1f;//one tenth float c = (float)distToEdge * 0.1f;//one tenth
return new ReciprocalFloatFunction(makeDistanceValueSource(queryShape.getCenter()), 1f, c, c); return new ReciprocalFloatFunction(makeDistanceValueSource(queryShape.getCenter(), 1.0), 1f, c, c);
} }
@Override @Override

View File

@ -135,9 +135,9 @@ public class BBoxStrategy extends SpatialStrategy {
//--------------------------------- //---------------------------------
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint) { public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
return new BBoxSimilarityValueSource( return new BBoxSimilarityValueSource(
this, new DistanceSimilarity(this.getSpatialContext(), queryPoint)); this, new DistanceSimilarity(this.getSpatialContext(), queryPoint, multiplier));
} }
public ValueSource makeBBoxAreaSimilarityValueSource(Rectangle queryBox) { public ValueSource makeBBoxAreaSimilarityValueSource(Rectangle queryBox) {

View File

@ -30,13 +30,15 @@ import org.apache.lucene.search.Explanation;
*/ */
public class DistanceSimilarity implements BBoxSimilarity { public class DistanceSimilarity implements BBoxSimilarity {
private final Point queryPoint; private final Point queryPoint;
private final double multiplier;
private final DistanceCalculator distCalc; private final DistanceCalculator distCalc;
private final double nullValue; private final double nullValue;
public DistanceSimilarity(SpatialContext ctx, Point queryPoint) { public DistanceSimilarity(SpatialContext ctx, Point queryPoint, double multiplier) {
this.queryPoint = queryPoint; this.queryPoint = queryPoint;
this.multiplier = multiplier;
this.distCalc = ctx.getDistCalc(); this.distCalc = ctx.getDistCalc();
this.nullValue = (ctx.isGeo() ? 180 : Double.MAX_VALUE); this.nullValue = (ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
} }
@Override @Override
@ -45,14 +47,43 @@ public class DistanceSimilarity implements BBoxSimilarity {
if (indexRect == null) { if (indexRect == null) {
score = nullValue; score = nullValue;
} else { } else {
score = distCalc.distance(queryPoint, indexRect.getCenter()); score = distCalc.distance(queryPoint, indexRect.getCenter()) * multiplier;
} }
if (exp != null) { if (exp != null) {
exp.setValue((float)score); exp.setValue((float)score);
exp.setDescription(this.getClass().getSimpleName()); exp.setDescription(this.getClass().getSimpleName());
exp.addDetail(new Explanation(-1f,""+queryPoint)); exp.addDetail(new Explanation(-1f, "" + queryPoint));
exp.addDetail(new Explanation(-1f,""+indexRect)); exp.addDetail(new Explanation(-1f,""+indexRect));
exp.addDetail(new Explanation((float)multiplier,"multiplier"));
} }
return score; return score;
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DistanceSimilarity that = (DistanceSimilarity) o;
if (Double.compare(that.multiplier, multiplier) != 0) return false;
if (Double.compare(that.nullValue, nullValue) != 0) return false;
if (!distCalc.equals(that.distCalc)) return false;
if (!queryPoint.equals(that.queryPoint)) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
result = queryPoint.hashCode();
temp = Double.doubleToLongBits(multiplier);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + distCalc.hashCode();
temp = Double.doubleToLongBits(nullValue);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
} }

View File

@ -56,7 +56,7 @@ import java.util.concurrent.ConcurrentHashMap;
* <li>Only {@link org.apache.lucene.spatial.query.SpatialOperation#Intersects} * <li>Only {@link org.apache.lucene.spatial.query.SpatialOperation#Intersects}
* is supported. If only points are indexed then this is effectively equivalent * is supported. If only points are indexed then this is effectively equivalent
* to IsWithin.</li> * to IsWithin.</li>
* <li>The strategy supports {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point)} * <li>The strategy supports {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point,double)}
* even for multi-valued data, so long as the indexed data is all points; the * even for multi-valued data, so long as the indexed data is all points; the
* behavior is undefined otherwise. However, <em>it will likely be removed in * behavior is undefined otherwise. However, <em>it will likely be removed in
* the future</em> in lieu of using another strategy with a more scalable * the future</em> in lieu of using another strategy with a more scalable
@ -182,7 +182,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
} }
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint) { public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() ); PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() );
if( p == null ) { if( p == null ) {
synchronized (this) {//double checked locking idiom is okay since provider is threadsafe synchronized (this) {//double checked locking idiom is okay since provider is threadsafe
@ -194,7 +194,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
} }
} }
return new ShapeFieldCacheDistanceValueSource(ctx, p, queryPoint); return new ShapeFieldCacheDistanceValueSource(ctx, p, queryPoint, multiplier);
} }
public SpatialPrefixTree getGrid() { public SpatialPrefixTree getGrid() {

View File

@ -38,14 +38,17 @@ import java.util.Map;
*/ */
public class ShapeFieldCacheDistanceValueSource extends ValueSource { public class ShapeFieldCacheDistanceValueSource extends ValueSource {
private final ShapeFieldCacheProvider<Point> provider;
private final SpatialContext ctx; private final SpatialContext ctx;
private final Point from; private final Point from;
private final ShapeFieldCacheProvider<Point> provider;
private final double multiplier;
public ShapeFieldCacheDistanceValueSource(SpatialContext ctx, ShapeFieldCacheProvider<Point> provider, Point from) { public ShapeFieldCacheDistanceValueSource(SpatialContext ctx,
ShapeFieldCacheProvider<Point> provider, Point from, double multiplier) {
this.ctx = ctx; this.ctx = ctx;
this.from = from; this.from = from;
this.provider = provider; this.provider = provider;
this.multiplier = multiplier;
} }
@Override @Override
@ -60,7 +63,7 @@ public class ShapeFieldCacheDistanceValueSource extends ValueSource {
provider.getCache(readerContext.reader()); provider.getCache(readerContext.reader());
private final Point from = ShapeFieldCacheDistanceValueSource.this.from; private final Point from = ShapeFieldCacheDistanceValueSource.this.from;
private final DistanceCalculator calculator = ctx.getDistCalc(); private final DistanceCalculator calculator = ctx.getDistCalc();
private final double nullValue = (ctx.isGeo() ? 180 : Double.MAX_VALUE); private final double nullValue = (ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
@Override @Override
public float floatVal(int doc) { public float floatVal(int doc) {
@ -69,13 +72,14 @@ public class ShapeFieldCacheDistanceValueSource extends ValueSource {
@Override @Override
public double doubleVal(int doc) { public double doubleVal(int doc) {
List<Point> vals = cache.getShapes( doc ); List<Point> vals = cache.getShapes( doc );
if( vals != null ) { if( vals != null ) {
double v = calculator.distance(from, vals.get(0)); double v = calculator.distance(from, vals.get(0));
for( int i=1; i<vals.size(); i++ ) { for( int i=1; i<vals.size(); i++ ) {
v = Math.min(v, calculator.distance(from, vals.get(i))); v = Math.min(v, calculator.distance(from, vals.get(i)));
} }
return v; return v * multiplier;
} }
return nullValue; return nullValue;
} }
@ -97,6 +101,7 @@ public class ShapeFieldCacheDistanceValueSource extends ValueSource {
if (!ctx.equals(that.ctx)) return false; if (!ctx.equals(that.ctx)) return false;
if (!from.equals(that.from)) return false; if (!from.equals(that.from)) return false;
if (!provider.equals(that.provider)) return false; if (!provider.equals(that.provider)) return false;
if (multiplier != that.multiplier) return false;
return true; return true;
} }

View File

@ -39,13 +39,15 @@ public class DistanceValueSource extends ValueSource {
private PointVectorStrategy strategy; private PointVectorStrategy strategy;
private final Point from; private final Point from;
private final double multiplier;
/** /**
* Constructor. * Constructor.
*/ */
public DistanceValueSource(PointVectorStrategy strategy, Point from) { public DistanceValueSource(PointVectorStrategy strategy, Point from, double multiplier) {
this.strategy = strategy; this.strategy = strategy;
this.from = from; this.from = from;
this.multiplier = multiplier;
} }
/** /**
@ -72,7 +74,8 @@ public class DistanceValueSource extends ValueSource {
private final Point from = DistanceValueSource.this.from; private final Point from = DistanceValueSource.this.from;
private final DistanceCalculator calculator = strategy.getSpatialContext().getDistCalc(); private final DistanceCalculator calculator = strategy.getSpatialContext().getDistCalc();
private final double nullValue = (strategy.getSpatialContext().isGeo() ? 180 : Double.MAX_VALUE); private final double nullValue =
(strategy.getSpatialContext().isGeo() ? 180 * multiplier : Double.MAX_VALUE);
@Override @Override
public float floatVal(int doc) { public float floatVal(int doc) {
@ -84,7 +87,7 @@ public class DistanceValueSource extends ValueSource {
// make sure it has minX and area // make sure it has minX and area
if (validX.get(doc)) { if (validX.get(doc)) {
assert validY.get(doc); assert validY.get(doc);
return calculator.distance(from, ptX.get(doc), ptY.get(doc)); return calculator.distance(from, ptX.get(doc), ptY.get(doc)) * multiplier;
} }
return nullValue; return nullValue;
} }
@ -105,6 +108,7 @@ public class DistanceValueSource extends ValueSource {
if (!from.equals(that.from)) return false; if (!from.equals(that.from)) return false;
if (!strategy.equals(that.strategy)) return false; if (!strategy.equals(that.strategy)) return false;
if (multiplier != that.multiplier) return false;
return true; return true;
} }

View File

@ -120,8 +120,8 @@ public class PointVectorStrategy extends SpatialStrategy {
} }
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint) { public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
return new DistanceValueSource(this, queryPoint); return new DistanceValueSource(this, queryPoint, multiplier);
} }
@Override @Override

View File

@ -34,6 +34,7 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class DistanceStrategyTest extends StrategyTestCase { public class DistanceStrategyTest extends StrategyTestCase {
@ -121,6 +122,11 @@ public class DistanceStrategyTest extends StrategyTestCase {
void checkDistValueSource(String ptStr, float... distances) throws IOException { void checkDistValueSource(String ptStr, float... distances) throws IOException {
Point pt = (Point) ctx.readShape(ptStr); Point pt = (Point) ctx.readShape(ptStr);
checkValueSource(strategy.makeDistanceValueSource(pt), distances, 1.0e-4f); float multiplier = random().nextFloat() * 100f;
float[] dists2 = Arrays.copyOf(distances, distances.length);
for (int i = 0; i < dists2.length; i++) {
dists2[i] *= multiplier;
}
checkValueSource(strategy.makeDistanceValueSource(pt, multiplier), dists2, 1.0e-3f);
} }
} }

View File

@ -162,7 +162,8 @@ public class SpatialExample extends LuceneTestCase {
//--Match all, order by distance ascending //--Match all, order by distance ascending
{ {
Point pt = ctx.makePoint(60, -50); Point pt = ctx.makePoint(60, -50);
ValueSource valueSource = strategy.makeDistanceValueSource(pt);//the distance (in degrees) double degToKm = DistanceUtils.degrees2Dist(1, DistanceUtils.EARTH_MEAN_RADIUS_KM);
ValueSource valueSource = strategy.makeDistanceValueSource(pt, degToKm);//the distance (in km)
Sort distSort = new Sort(valueSource.getSortField(false)).rewrite(indexSearcher);//false=asc dist Sort distSort = new Sort(valueSource.getSortField(false)).rewrite(indexSearcher);//false=asc dist
TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort); TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort);
assertDocMatchedIds(indexSearcher, docs, 4, 20, 2); assertDocMatchedIds(indexSearcher, docs, 4, 20, 2);

View File

@ -240,12 +240,14 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
//We get the valueSource for the score then the filter and combine them. //We get the valueSource for the score then the filter and combine them.
ValueSource valueSource; ValueSource valueSource;
if ("distance".equals(score)) if ("distance".equals(score)) {
valueSource = strategy.makeDistanceValueSource(spatialArgs.getShape().getCenter()); double multiplier = 1.0;//TODO support units=kilometers
else if ("recipDistance".equals(score)) valueSource = strategy.makeDistanceValueSource(spatialArgs.getShape().getCenter(), multiplier);
} else if ("recipDistance".equals(score)) {
valueSource = strategy.makeRecipDistanceValueSource(spatialArgs.getShape()); valueSource = strategy.makeRecipDistanceValueSource(spatialArgs.getShape());
else } else {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "'score' local-param must be one of 'none', 'distance', or 'recipDistance'"); throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "'score' local-param must be one of 'none', 'distance', or 'recipDistance'");
}
FunctionQuery functionQuery = new FunctionQuery(valueSource); FunctionQuery functionQuery = new FunctionQuery(valueSource);
if (localParams != null && !localParams.getBool(FILTER_PARAM, true)) if (localParams != null && !localParams.getBool(FILTER_PARAM, true))