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 ShapeFieldCacheProvider provider; - private final DistanceCalculator calculator; + private final SpatialContext ctx; private final Point from; - public ShapeFieldCacheDistanceValueSource(Point from, DistanceCalculator calc, ShapeFieldCacheProvider provider) { + public ShapeFieldCacheDistanceValueSource(SpatialContext ctx, ShapeFieldCacheProvider provider, Point from) { + this.ctx = ctx; this.from = from; this.provider = provider; - this.calculator = calc; } @Override public String description() { - return getClass().getSimpleName()+"("+calculator+")"; + return getClass().getSimpleName()+"("+provider+", "+from+")"; } @Override - public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { - final ShapeFieldCache cache = - provider.getCache(readerContext.reader()); - + public FunctionValues getValues(Map context, final AtomicReaderContext readerContext) throws IOException { return new FunctionValues() { + private final ShapeFieldCache cache = + 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); + @Override public float floatVal(int doc) { return (float) doubleVal(doc); @@ -73,7 +77,7 @@ public class ShapeFieldCacheDistanceValueSource extends ValueSource { } return v; } - return Double.NaN; // ?? maybe max? + return nullValue; } @Override @@ -90,16 +94,15 @@ public class ShapeFieldCacheDistanceValueSource extends ValueSource { ShapeFieldCacheDistanceValueSource that = (ShapeFieldCacheDistanceValueSource) o; - if (calculator != null ? !calculator.equals(that.calculator) : that.calculator != null) return false; - if (from != null ? !from.equals(that.from) : that.from != null) return false; + if (!ctx.equals(that.ctx)) return false; + if (!from.equals(that.from)) return false; + if (!provider.equals(that.provider)) return false; return true; } @Override public int hashCode() { - int 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/DistanceValueSource.java b/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java index 99a84ca0595..cd2750d4eb0 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java @@ -30,7 +30,7 @@ import java.io.IOException; import java.util.Map; /** - * An implementation of the Lucene ValueSource model to support spatial relevance ranking. + * An implementation of the Lucene ValueSource model that returns the distance. * * @lucene.internal */ @@ -38,15 +38,13 @@ public class DistanceValueSource extends ValueSource { private TwoDoublesStrategy strategy; private final Point from; - private final DistanceCalculator calculator; /** * Constructor. */ - public DistanceValueSource(TwoDoublesStrategy strategy, Point from, DistanceCalculator calc) { + public DistanceValueSource(TwoDoublesStrategy strategy, Point from) { this.strategy = strategy; this.from = from; - this.calculator = calc; } /** @@ -54,10 +52,9 @@ public class DistanceValueSource extends ValueSource { */ @Override public String description() { - return "DistanceValueSource("+calculator+")"; + return "DistanceValueSource("+strategy+", "+from+")"; } - /** * Returns the FunctionValues used by the function query. */ @@ -71,6 +68,11 @@ public class DistanceValueSource extends ValueSource { final Bits validY = FieldCache.DEFAULT.getDocsWithField(reader, strategy.getFieldNameY()); return new FunctionValues() { + + 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); + @Override public float floatVal(int doc) { return (float) doubleVal(doc); @@ -79,10 +81,11 @@ public class DistanceValueSource extends ValueSource { @Override public double doubleVal(int doc) { // make sure it has minX and area - if (validX.get(doc) && validY.get(doc)) { + if (validX.get(doc)) { + assert validY.get(doc); return calculator.distance(from, ptX[doc], ptY[doc]); } - return 0; + return nullValue; } @Override @@ -92,11 +95,6 @@ public class DistanceValueSource extends ValueSource { }; } - /** - * Determines if this ValueSource is equal to another. - * @param o the ValueSource to compare - * @return true 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 parameters() { + List ctorArgs = new ArrayList(); + + SpatialContext ctx = SpatialContext.GEO; + SpatialPrefixTree grid; + SpatialStrategy strategy; + + grid = new QuadPrefixTree(ctx,25); + strategy = new RecursivePrefixTreeStrategy(grid, "recursive_quad"); + ctorArgs.add(new Object[]{new Param(strategy)}); + + grid = new GeohashPrefixTree(ctx,12); + strategy = new TermQueryPrefixTreeStrategy(grid, "termquery_geohash"); + ctorArgs.add(new Object[]{new Param(strategy)}); + + strategy = new TwoDoublesStrategy(ctx, "twodoubles"); + ctorArgs.add(new Object[]{new Param(strategy)}); + + strategy = new BBoxStrategy(ctx, "bbox"); + ctorArgs.add(new Object[]{new Param(strategy)}); + + return ctorArgs; + } + + // this is a hack for clover! + static class Param { + SpatialStrategy strategy; + + Param(SpatialStrategy strategy) { + this.strategy = strategy; + } + + @Override + public String toString() { + return strategy.getFieldName(); + } + } + +// private String fieldName; + + public DistanceStrategyTest(@Name("strategy") Param param) { + SpatialStrategy strategy = param.strategy; + this.ctx = strategy.getSpatialContext(); + this.strategy = strategy; + } + + @Test + public void testDistanceOrder() throws IOException { + adoc("100", ctx.makePoint(2,1)); + adoc("101", ctx.makePoint(-1,4)); + adoc("103", (Shape)null);//test score for nothing + commit(); + //FYI distances are in docid order + checkDistValueSource("3,4", 2.8274937f, 5.0898066f, 180f); + checkDistValueSource("4,0", 3.6043684f, 0.9975641f, 180f); + } + + @Test + public void testRecipScore() throws IOException { + Point p100 = ctx.makePoint(2, 1); + adoc("100", p100); + Point p101 = ctx.makePoint(-1, 4); + adoc("101", p101); + adoc("103", (Shape)null);//test score for nothing + commit(); + + double dist = ctx.getDistCalc().distance(p100, p101); + Shape queryShape = ctx.makeCircle(2.01, 0.99, dist); + checkValueSource(strategy.makeRecipDistanceValueSource(queryShape), + new float[]{1.00f, 0.10f, 0f}, 0.09f); + } + + @Override + protected Document newDoc(String id, Shape shape) { + //called by adoc(). Make compatible with BBoxStrategy. + if (shape != null && strategy instanceof BBoxStrategy) + shape = ctx.makeRectangle(shape.getCenter(), shape.getCenter()); + return super.newDoc(id, shape); + } + + void checkDistValueSource(String ptStr, float... distances) throws IOException { + Point pt = (Point) ctx.readShape(ptStr); + checkValueSource(strategy.makeDistanceValueSource(pt), distances, 1.0e-4f); + } +} diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java b/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java index 00e261376e2..e02d93cb568 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java @@ -24,10 +24,6 @@ import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.io.ShapeReadWriter; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Shape; -import org.apache.lucene.document.Document; -import org.apache.lucene.document.Field; -import org.apache.lucene.document.StoredField; -import org.apache.lucene.document.StringField; import org.apache.lucene.search.FilteredQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; @@ -157,51 +153,7 @@ public class PortedSolr3Test extends StrategyTestCase { checkHitsBBox("43.517030,-96.789603", 110, 1, 17); } - /** - * This test is similar to a Solr 3 spatial test. - */ - @Test - public void testDistanceOrder() throws IOException { - adoc("100","1,2"); - adoc("101","4,-1"); - commit(); - double km1000inDeg = DistanceUtils.dist2Degrees(1000, DistanceUtils.EARTH_MEAN_RADIUS_KM); - - //query closer to #100 - checkHitsOrdered("Intersects(Circle(3,4 d="+km1000inDeg+"))", "101", "100"); - //query closer to #101 - checkHitsOrdered("Intersects(Circle(4,0 d="+km1000inDeg+"))", "100", "101"); - } - - private void checkHitsOrdered(String spatialQ, String... ids) { - SpatialArgs args = this.argsParser.parse(spatialQ,ctx); - Query query = strategy.makeQuery(args); - SearchResults results = executeQuery(query, 100); - String[] resultIds = new String[results.numFound]; - int i = 0; - for (SearchResult result : results.results) { - resultIds[i++] = result.document.get("id"); - } - assertArrayEquals("order matters",ids, resultIds); - } - //---- these are similar to Solr test methods - - private void adoc(String idStr, String shapeStr) throws IOException { - Shape shape = new ShapeReadWriter(ctx).readShape(shapeStr); - addDocument(newDoc(idStr,shape)); - } - - private Document newDoc(String id, Shape shape) { - Document doc = new Document(); - doc.add(new StringField("id", id, Field.Store.YES)); - for (Field f : strategy.createIndexableFields(shape)) { - doc.add(f); - } - if (storeShape) - doc.add(new StoredField(strategy.getFieldName(), ctx.toString(shape))); - return doc; - } private void checkHitsCircle(String ptStr, double distKM, int assertNumFound, int... assertIds) { _checkHits(false, ptStr, distKM, assertNumFound, assertIds); diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java b/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java index 9ef5cbe5ee9..7b517480904 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java @@ -20,6 +20,7 @@ package org.apache.lucene.spatial; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.io.ShapeReadWriter; +import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Shape; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; @@ -45,7 +46,6 @@ import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.Version; import java.io.IOException; @@ -75,8 +75,9 @@ public class SpatialExample extends LuceneTestCase { /** * The Lucene spatial {@link SpatialStrategy} encapsulates an approach to - * indexing and searching shapes, and providing relevancy scores for them. - * It's a simple API to unify different approaches. + * indexing and searching shapes, and providing distance values for them. + * It's a simple API to unify different approaches. You might use more than + * one strategy for a shape as each strategy has its strengths and weaknesses. *

* Note that these are initialized with a field name. */ @@ -85,13 +86,13 @@ public class SpatialExample extends LuceneTestCase { private Directory directory; protected void init() { - //Typical geospatial context with kilometer units. - // These can also be constructed from a factory: SpatialContextFactory + //Typical geospatial context + // These can also be constructed from SpatialContextFactory this.ctx = SpatialContext.GEO; - int maxLevels = 10;//results in sub-meter precision for geohash + int maxLevels = 11;//results in sub-meter precision for geohash //TODO demo lookup by detail distance - // This can also be constructed from a factory: SpatialPrefixTreeFactory + // This can also be constructed from SpatialPrefixTreeFactory SpatialPrefixTree grid = new GeohashPrefixTree(ctx, maxLevels); this.strategy = new RecursivePrefixTreeStrategy(grid, "myGeoField"); @@ -151,9 +152,8 @@ public class SpatialExample extends LuceneTestCase { } //--Match all, order by distance { - SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects,//doesn't matter - ctx.makePoint(60, -50)); - ValueSource valueSource = strategy.makeValueSource(args);//the distance (in degrees) + Point pt = ctx.makePoint(60, -50); + ValueSource valueSource = strategy.makeDistanceValueSource(pt);//the distance (in degrees) Sort reverseDistSort = new Sort(valueSource.getSortField(false)).rewrite(indexSearcher);//true=asc dist TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, reverseDistSort); assertDocMatchedIds(indexSearcher, docs, 4, 20, 2); diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java index 5909cb109b7..13592ba7836 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java @@ -32,14 +32,15 @@ import org.junit.After; import org.junit.Before; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public abstract class SpatialTestCase extends LuceneTestCase { private DirectoryReader indexReader; private RandomIndexWriter indexWriter; private Directory directory; - private IndexSearcher indexSearcher; + protected IndexSearcher indexSearcher; @Override @Before diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java index b40358859dc..fea9913c97e 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java @@ -27,6 +27,11 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StringField; +import org.apache.lucene.queries.function.FunctionQuery; +import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.search.CheckHits; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; import org.apache.lucene.spatial.query.SpatialArgsParser; import org.junit.Assert; @@ -167,4 +172,47 @@ public abstract class StrategyTestCase extends SpatialTestCase { } } } + + protected void adoc(String id, String shapeStr) throws IOException { + Shape shape = shapeStr==null ? null : new ShapeReadWriter(ctx).readShape(shapeStr); + addDocument(newDoc(id, shape)); + } + protected void adoc(String id, Shape shape) throws IOException { + addDocument(newDoc(id, shape)); + } + + protected Document newDoc(String id, Shape shape) { + Document doc = new Document(); + doc.add(new StringField("id", id, Field.Store.YES)); + if (shape != null) { + for (Field f : strategy.createIndexableFields(shape)) { + doc.add(f); + } + if (storeShape) + doc.add(new StoredField(strategy.getFieldName(), ctx.toString(shape))); + } + return doc; + } + + /** scores[] are in docId order */ + protected void checkValueSource(ValueSource vs, float scores[], float delta) throws IOException { + FunctionQuery q = new FunctionQuery(vs); + +// //TODO is there any point to this check? +// int expectedDocs[] = new int[scores.length];//fill with ascending 0....length-1 +// for (int i = 0; i < expectedDocs.length; i++) { +// expectedDocs[i] = i; +// } +// CheckHits.checkHits(random(), q, "", indexSearcher, expectedDocs); + + TopDocs docs = indexSearcher.search(q, 1000);//calculates the score + for (int i = 0; i < docs.scoreDocs.length; i++) { + ScoreDoc gotSD = docs.scoreDocs[i]; + float expectedScore = scores[gotSD.doc]; + assertEquals("Not equal for doc "+gotSD.doc, expectedScore, gotSD.score, delta); + } + + CheckHits.checkExplanations(q, "", indexSearcher); + } + } diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java index 49d9e459889..4b52e3a9438 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java @@ -23,10 +23,6 @@ import com.spatial4j.core.io.GeohashUtils; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; -import org.apache.lucene.document.Document; -import org.apache.lucene.document.Field; -import org.apache.lucene.document.StoredField; -import org.apache.lucene.document.StringField; import org.apache.lucene.spatial.SpatialMatchConcern; import org.apache.lucene.spatial.StrategyTestCase; import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; @@ -198,17 +194,6 @@ public class TestRecursivePrefixTreeStrategy extends StrategyTestCase { } } - private Document newDoc(String id, Shape shape) { - Document doc = new Document(); - doc.add(new StringField("id", id, Field.Store.YES)); - for (Field f : strategy.createIndexableFields(shape)) { - doc.add(f); - } - if (storeShape) - doc.add(new StoredField(strategy.getFieldName(), ctx.toString(shape))); - return doc; - } - /** NGeohash round-trip for given precision. */ private Point alignGeohash(Point p) { return GeohashUtils.decode(GeohashUtils.encodeLatLon(p.getY(), p.getX(), maxLength), ctx); diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java b/lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java index ff8a54c9275..e6484386746 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java @@ -27,6 +27,7 @@ import org.apache.lucene.spatial.StrategyTestCase; import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialOperation; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.io.IOException; @@ -41,7 +42,7 @@ public class TestTwoDoublesStrategy extends StrategyTestCase { this.strategy = new TwoDoublesStrategy(ctx, getClass().getSimpleName()); } - @Test + @Test @Ignore public void testCircleShapeSupport() { Circle circle = ctx.makeCircle(ctx.makePoint(0, 0), 10); SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, circle);