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 7c9922e2fe1..c589d829954 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java @@ -18,13 +18,14 @@ package org.apache.lucene.spatial; */ import com.spatial4j.core.context.SpatialContext; +import com.spatial4j.core.shape.Point; +import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import org.apache.lucene.document.Field; -import org.apache.lucene.queries.function.FunctionQuery; import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.queries.function.valuesource.ReciprocalFloatFunction; +import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.Filter; -import org.apache.lucene.search.FilteredQuery; -import org.apache.lucene.search.Query; import org.apache.lucene.spatial.query.SpatialArgs; /** @@ -99,26 +100,50 @@ public abstract class SpatialStrategy { public abstract Field[] createIndexableFields(Shape shape); /** - * The value source yields a number that is proportional to the distance between the query shape and indexed data. + * 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. */ - public abstract ValueSource makeValueSource(SpatialArgs args); + public abstract ValueSource makeDistanceValueSource(Point queryPoint); /** - * Make a query which has a score based on the distance from the data to the query shape. - * The default implementation constructs a {@link FilteredQuery} based on - * {@link #makeFilter(org.apache.lucene.spatial.query.SpatialArgs)} and - * {@link #makeValueSource(org.apache.lucene.spatial.query.SpatialArgs)}. + * Make a (ConstantScore) Query based principally on {@link org.apache.lucene.spatial.query.SpatialOperation} + * and {@link Shape} from the supplied {@code args}. + * The default implementation is + *
return new ConstantScoreQuery(makeFilter(args));*/ - public Query makeQuery(SpatialArgs args) { - Filter filter = makeFilter(args); - ValueSource vs = makeValueSource(args); - return new FilteredQuery(new FunctionQuery(vs), filter); + public ConstantScoreQuery makeQuery(SpatialArgs args) { + return new ConstantScoreQuery(makeFilter(args)); } + /** - * Make a Filter + * Make a Filter based principally on {@link org.apache.lucene.spatial.query.SpatialOperation} + * and {@link Shape} from the supplied {@code args}. + * + * If a subclasses implements + * {@link #makeQuery(org.apache.lucene.spatial.query.SpatialArgs)} + * then this method could be simply: + *
return new QueryWrapperFilter(makeQuery(args).getQuery());*/ public abstract Filter makeFilter(SpatialArgs args); + /** + * Returns a ValueSource with values ranging from 1 to 0, depending inversely + * on the distance from {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point)}. + * The formula is
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
+ * low as ~0.1 at its furthest edges.
+ */
+ public final ValueSource makeRecipDistanceValueSource(Shape queryShape) {
+ Rectangle bbox = queryShape.getBoundingBox();
+ double diagonalDist = ctx.getDistCalc().distance(
+ 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);
+ }
+
@Override
public String toString() {
return getClass().getSimpleName()+" field:"+fieldName+" ctx="+ctx;
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java
index c6affee1f9b..58e1acd85bf 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java
@@ -84,8 +84,9 @@ public class BBoxSimilarityValueSource extends ValueSource {
minX[doc], maxX[doc],
minY[doc], maxY[doc]);
return (float) similarity.score(rect, null);
+ } else {
+ return (float) similarity.score(null, null);
}
- return 0;
}
public Explanation explain(int doc) {
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 0a90345f965..356b24bca40 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
@@ -18,6 +18,7 @@ package org.apache.lucene.spatial.bbox;
*/
import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.document.DoubleField;
@@ -114,29 +115,33 @@ public class BBoxStrategy extends SpatialStrategy {
//---------------------------------
@Override
- public ValueSource makeValueSource(SpatialArgs args) {
- Shape shape = args.getShape();
- if (!(shape instanceof Rectangle))
- throw new IllegalArgumentException("Can only get valueSource by Rectangle, not " + shape);
+ public ValueSource makeDistanceValueSource(Point queryPoint) {
return new BBoxSimilarityValueSource(
- this, new AreaSimilarity((Rectangle)shape, queryPower, targetPower));
+ this, new DistanceSimilarity(this.getSpatialContext(), queryPoint));
}
+ public ValueSource makeBBoxAreaSimilarityValueSource(Rectangle queryBox) {
+ return new BBoxSimilarityValueSource(
+ this, new AreaSimilarity(queryBox, queryPower, targetPower));
+ }
@Override
public Filter makeFilter(SpatialArgs args) {
- Query spatial = makeSpatialQuery(args);
- return new QueryWrapperFilter( spatial );
+ return new QueryWrapperFilter(makeSpatialQuery(args));
}
@Override
- public Query makeQuery(SpatialArgs args) {
+ public ConstantScoreQuery makeQuery(SpatialArgs args) {
+ return new ConstantScoreQuery(makeSpatialQuery(args));
+ }
+
+ public Query makeQueryWithValueSource(SpatialArgs args, ValueSource valueSource) {
BooleanQuery bq = new BooleanQuery();
Query spatial = makeSpatialQuery(args);
bq.add(new ConstantScoreQuery(spatial), BooleanClause.Occur.MUST);
-
+
// This part does the scoring
- Query spatialRankingQuery = new FunctionQuery(makeValueSource(args));
+ Query spatialRankingQuery = new FunctionQuery(valueSource);
bq.add(spatialRankingQuery, BooleanClause.Occur.MUST);
return bq;
}
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
new file mode 100644
index 00000000000..81af9b03a98
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java
@@ -0,0 +1,58 @@
+package org.apache.lucene.spatial.bbox;
+
+/*
+ * 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.
+ */
+
+import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.distance.DistanceCalculator;
+import com.spatial4j.core.shape.Point;
+import com.spatial4j.core.shape.Rectangle;
+import org.apache.lucene.search.Explanation;
+
+/**
+ * Returns the distance between the center of the indexed rectangle and the
+ * query shape.
+ * @lucene.experimental
+ */
+public class DistanceSimilarity implements BBoxSimilarity {
+ private final Point queryPoint;
+ private final DistanceCalculator distCalc;
+ private final double nullValue;
+
+ public DistanceSimilarity(SpatialContext ctx, Point queryPoint) {
+ this.queryPoint = queryPoint;
+ this.distCalc = ctx.getDistCalc();
+ this.nullValue = (ctx.isGeo() ? 180 : Double.MAX_VALUE);
+ }
+
+ @Override
+ public double score(Rectangle indexRect, Explanation exp) {
+ double score;
+ if (indexRect == null) {
+ score = nullValue;
+ } else {
+ score = distCalc.distance(queryPoint, indexRect.getCenter());
+ }
+ if (exp != null) {
+ exp.setValue((float)score);
+ exp.setDescription(this.getClass().getSimpleName());
+ exp.addDetail(new Explanation(-1f,""+queryPoint));
+ exp.addDetail(new Explanation(-1f,""+indexRect));
+ }
+ return score;
+ }
+}
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 93097944131..66226035750 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
@@ -17,7 +17,6 @@ package org.apache.lucene.spatial.prefix;
* limitations under the License.
*/
-import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.analysis.TokenStream;
@@ -141,12 +140,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
}
@Override
- public ValueSource makeValueSource(SpatialArgs args) {
- DistanceCalculator calc = grid.getSpatialContext().getDistCalc();
- return makeValueSource(args, calc);
- }
-
- public ValueSource makeValueSource(SpatialArgs args, DistanceCalculator calc) {
+ public ValueSource makeDistanceValueSource(Point queryPoint) {
PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() );
if( p == null ) {
synchronized (this) {//double checked locking idiom is okay since provider is threadsafe
@@ -157,8 +151,8 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
}
}
}
- Point point = args.getShape().getCenter();
- return new ShapeFieldCacheDistanceValueSource(point, calc, p);
+
+ return new ShapeFieldCacheDistanceValueSource(ctx, p, queryPoint);
}
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 c2d8b0f61fe..3d7ed14a05d 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
@@ -17,6 +17,7 @@ package org.apache.lucene.spatial.util;
* limitations under the License.
*/
+import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.shape.Point;
import org.apache.lucene.index.AtomicReaderContext;
@@ -38,26 +39,29 @@ import java.util.Map;
public class ShapeFieldCacheDistanceValueSource extends ValueSource {
private final ShapeFieldCacheProvidertrue
if the two objects are based upon the same query envelope
- */
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -104,18 +102,14 @@ public class DistanceValueSource extends ValueSource {
DistanceValueSource that = (DistanceValueSource) o;
- if (calculator != null ? !calculator.equals(that.calculator) : that.calculator != null) return false;
- if (strategy != null ? !strategy.equals(that.strategy) : that.strategy != null) return false;
- if (from != null ? !from.equals(that.from) : that.from != null) return false;
+ if (!from.equals(that.from)) return false;
+ if (!strategy.equals(that.strategy)) return false;
return true;
}
@Override
public int hashCode() {
- int result = strategy != null ? strategy.hashCode() : 0;
- result = 31 * result + (calculator != null ? calculator.hashCode() : 0);
- result = 31 * result + (from != null ? from.hashCode() : 0);
- return result;
+ return from.hashCode();
}
}
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java b/lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java
index cf405b0cd31..3e8d4479072 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java
@@ -30,6 +30,7 @@ import org.apache.lucene.queries.function.FunctionQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
@@ -94,32 +95,32 @@ public class TwoDoublesStrategy extends SpatialStrategy {
}
@Override
- public ValueSource makeValueSource(SpatialArgs args) {
- Point p = args.getShape().getCenter();
- return new DistanceValueSource(this, p, ctx.getDistCalc());
+ public ValueSource makeDistanceValueSource(Point queryPoint) {
+ return new DistanceValueSource(this, queryPoint);
}
@Override
public Filter makeFilter(SpatialArgs args) {
- if( args.getShape() instanceof Circle) {
- if( SpatialOperation.is( args.getOperation(),
- SpatialOperation.Intersects,
- SpatialOperation.IsWithin )) {
- Circle circle = (Circle)args.getShape();
- Query bbox = makeWithin(circle.getBoundingBox());
-
- // Make the ValueSource
- ValueSource valueSource = makeValueSource(args);
-
- return new ValueSourceFilter(
- new QueryWrapperFilter( bbox ), valueSource, 0, circle.getRadius() );
- }
- }
- return new QueryWrapperFilter( makeQuery(args) );
+ return new QueryWrapperFilter(makeQuery(args).getQuery());
}
@Override
- public Query makeQuery(SpatialArgs args) {
+ public ConstantScoreQuery makeQuery(SpatialArgs args) {
+ if(! SpatialOperation.is( args.getOperation(),
+ SpatialOperation.Intersects,
+ SpatialOperation.IsWithin ))
+ throw new UnsupportedSpatialOperation(args.getOperation());
+ Shape shape = args.getShape();
+ if (!(shape instanceof Rectangle))
+ throw new InvalidShapeException("Only Rectangle is currently supported, got "+shape.getClass());
+ Rectangle bbox = (Rectangle) shape;
+ if (bbox.getCrossesDateLine()) {
+ throw new UnsupportedOperationException( "Crossing dateline not yet supported" );
+ }
+ return new ConstantScoreQuery(makeWithin(bbox));
+ }
+
+ public Query makeQueryDistanceScore(SpatialArgs args) {
// For starters, just limit the bbox
Shape shape = args.getShape();
if (!(shape instanceof Rectangle || shape instanceof Circle)) {
@@ -151,7 +152,7 @@ public class TwoDoublesStrategy extends SpatialStrategy {
Circle circle = (Circle)args.getShape();
// Make the ValueSource
- valueSource = makeValueSource(args);
+ valueSource = makeDistanceValueSource(shape.getCenter());
ValueSourceFilter vsf = new ValueSourceFilter(
new QueryWrapperFilter( spatial ), valueSource, 0, circle.getRadius() );
@@ -171,7 +172,7 @@ public class TwoDoublesStrategy extends SpatialStrategy {
valueSource = new CachingDoubleValueSource(valueSource);
}
else {
- valueSource = makeValueSource(args);
+ valueSource = makeDistanceValueSource(shape.getCenter());
}
Query spatialRankingQuery = new FunctionQuery(valueSource);
BooleanQuery bq = new BooleanQuery();
@@ -212,19 +213,19 @@ public class TwoDoublesStrategy extends SpatialStrategy {
*/
Query makeDisjoint(Rectangle bbox) {
Query qX = NumericRangeQuery.newDoubleRange(
- fieldNameX,
- precisionStep,
- bbox.getMinX(),
- bbox.getMaxX(),
- true,
- true);
+ fieldNameX,
+ precisionStep,
+ bbox.getMinX(),
+ bbox.getMaxX(),
+ true,
+ true);
Query qY = NumericRangeQuery.newDoubleRange(
- fieldNameY,
- precisionStep,
- bbox.getMinY(),
- bbox.getMaxY(),
- true,
- true);
+ fieldNameY,
+ precisionStep,
+ bbox.getMinY(),
+ bbox.getMaxY(),
+ true,
+ true);
BooleanQuery bq = new BooleanQuery();
bq.add(qX,BooleanClause.Occur.MUST_NOT);
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
new file mode 100644
index 00000000000..f9db29f05e8
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
@@ -0,0 +1,129 @@
+package org.apache.lucene.spatial;
+
+/*
+ * 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.
+ */
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.shape.Point;
+import com.spatial4j.core.shape.Shape;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.spatial.bbox.BBoxStrategy;
+import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
+import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
+import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
+import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
+import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
+import org.apache.lucene.spatial.vector.TwoDoublesStrategy;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author David Smiley - dsmiley@mitre.org
+ */
+public class DistanceStrategyTest extends StrategyTestCase {
+
+ @ParametersFactory
+ public static Iterable