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
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
* 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);
/**
* 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
* 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}
@ -139,7 +148,7 @@ public abstract class SpatialStrategy {
/**
* 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
* 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
@ -151,7 +160,7 @@ public abstract class SpatialStrategy {
ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY());
double distToEdge = diagonalDist * 0.5;
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

View File

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

View File

@ -30,13 +30,15 @@ import org.apache.lucene.search.Explanation;
*/
public class DistanceSimilarity implements BBoxSimilarity {
private final Point queryPoint;
private final double multiplier;
private final DistanceCalculator distCalc;
private final double nullValue;
public DistanceSimilarity(SpatialContext ctx, Point queryPoint) {
public DistanceSimilarity(SpatialContext ctx, Point queryPoint, double multiplier) {
this.queryPoint = queryPoint;
this.multiplier = multiplier;
this.distCalc = ctx.getDistCalc();
this.nullValue = (ctx.isGeo() ? 180 : Double.MAX_VALUE);
this.nullValue = (ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
}
@Override
@ -45,14 +47,43 @@ public class DistanceSimilarity implements BBoxSimilarity {
if (indexRect == null) {
score = nullValue;
} else {
score = distCalc.distance(queryPoint, indexRect.getCenter());
score = distCalc.distance(queryPoint, indexRect.getCenter()) * multiplier;
}
if (exp != null) {
exp.setValue((float)score);
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((float)multiplier,"multiplier"));
}
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}
* is supported. If only points are indexed then this is effectively equivalent
* 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
* 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
@ -182,7 +182,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint) {
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() );
if( p == null ) {
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() {

View File

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

View File

@ -39,13 +39,15 @@ public class DistanceValueSource extends ValueSource {
private PointVectorStrategy strategy;
private final Point from;
private final double multiplier;
/**
* Constructor.
*/
public DistanceValueSource(PointVectorStrategy strategy, Point from) {
public DistanceValueSource(PointVectorStrategy strategy, Point from, double multiplier) {
this.strategy = strategy;
this.from = from;
this.multiplier = multiplier;
}
/**
@ -72,7 +74,8 @@ public class DistanceValueSource extends ValueSource {
private final Point from = DistanceValueSource.this.from;
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
public float floatVal(int doc) {
@ -84,7 +87,7 @@ public class DistanceValueSource extends ValueSource {
// make sure it has minX and area
if (validX.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;
}
@ -105,6 +108,7 @@ public class DistanceValueSource extends ValueSource {
if (!from.equals(that.from)) return false;
if (!strategy.equals(that.strategy)) return false;
if (multiplier != that.multiplier) return false;
return true;
}

View File

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

View File

@ -34,6 +34,7 @@ import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DistanceStrategyTest extends StrategyTestCase {
@ -121,6 +122,11 @@ public class DistanceStrategyTest extends StrategyTestCase {
void checkDistValueSource(String ptStr, float... distances) throws IOException {
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
{
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
TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort);
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.
ValueSource valueSource;
if ("distance".equals(score))
valueSource = strategy.makeDistanceValueSource(spatialArgs.getShape().getCenter());
else if ("recipDistance".equals(score))
if ("distance".equals(score)) {
double multiplier = 1.0;//TODO support units=kilometers
valueSource = strategy.makeDistanceValueSource(spatialArgs.getShape().getCenter(), multiplier);
} else if ("recipDistance".equals(score)) {
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'");
}
FunctionQuery functionQuery = new FunctionQuery(valueSource);
if (localParams != null && !localParams.getBool(FILTER_PARAM, true))