From 47bf70812bae1eb6de20cd15624a2ab4d53b0668 Mon Sep 17 00:00:00 2001 From: David Wayne Smiley Date: Wed, 24 Jul 2013 17:20:10 +0000 Subject: [PATCH] LUCENE-5118: multiplier to spatial makeDistanceValueSource git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1506632 13f79535-47bb-0310-9956-ffa450edef68 --- lucene/CHANGES.txt | 3 ++ .../lucene/spatial/SpatialStrategy.java | 17 ++++++-- .../lucene/spatial/bbox/BBoxStrategy.java | 4 +- .../spatial/bbox/DistanceSimilarity.java | 39 +++++++++++++++++-- .../spatial/prefix/PrefixTreeStrategy.java | 6 +-- .../ShapeFieldCacheDistanceValueSource.java | 13 +++++-- .../spatial/vector/DistanceValueSource.java | 10 +++-- .../spatial/vector/PointVectorStrategy.java | 4 +- .../lucene/spatial/DistanceStrategyTest.java | 8 +++- .../apache/lucene/spatial/SpatialExample.java | 3 +- .../solr/schema/AbstractSpatialFieldType.java | 10 +++-- 11 files changed, 89 insertions(+), 28 deletions(-) diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 68cbb43acc3..5b7cbdfae34 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -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 diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java b/lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java index de09406870e..f229c874b38 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java @@ -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 diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java index 7b2204ff478..ced3f65f7c7 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java @@ -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) { diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java index 81af9b03a98..f2a44026dbf 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java @@ -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; + } } diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java index 59f659d2eb4..297889c7874 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java @@ -56,7 +56,7 @@ import java.util.concurrent.ConcurrentHashMap; *
  • Only {@link org.apache.lucene.spatial.query.SpatialOperation#Intersects} * is supported. If only points are indexed then this is effectively equivalent * to IsWithin.
  • - *
  • The strategy supports {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point)} + *
  • 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, it will likely be removed in * the future 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() { diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java index 3d7ed14a05d..b99e9de7319 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java @@ -38,14 +38,17 @@ import java.util.Map; */ public class ShapeFieldCacheDistanceValueSource extends ValueSource { - private final ShapeFieldCacheProvider provider; private final SpatialContext ctx; private final Point from; + private final ShapeFieldCacheProvider provider; + private final double multiplier; - public ShapeFieldCacheDistanceValueSource(SpatialContext ctx, ShapeFieldCacheProvider provider, Point from) { + public ShapeFieldCacheDistanceValueSource(SpatialContext ctx, + ShapeFieldCacheProvider 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 vals = cache.getShapes( doc ); if( vals != null ) { double v = calculator.distance(from, vals.get(0)); for( int i=1; i 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))