Index: lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java (revision 1384483) +++ lucene/spatial/src/test/org/apache/lucene/spatial/SpatialExample.java (revision ) @@ -20,6 +20,7 @@ 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.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 @@ /** * 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 @@ 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 @@ } //--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); Index: lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java (revision 1384483) +++ lucene/spatial/src/test/org/apache/lucene/spatial/vector/TestTwoDoublesStrategy.java (revision ) @@ -27,6 +27,7 @@ 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 @@ 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); Index: lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/SpatialStrategy.java (revision ) @@ -18,13 +18,15 @@ */ 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.search.QueryWrapperFilter; import org.apache.lucene.spatial.query.SpatialArgs; /** @@ -50,6 +52,11 @@ * values of shapes, which is immaterial to indexing & search. *

* Thread-safe. + *

+ * Implementation note: subclasses must override at least one of + * {@link #makeFilter(org.apache.lucene.spatial.query.SpatialArgs)} or + * {@link #makeQuery(org.apache.lucene.spatial.query.SpatialArgs)} since + * the default implementations of each call the other. * * @lucene.experimental */ @@ -99,25 +106,47 @@ 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 closest distance between the center of the + * indexed shape and {@code queryPoint}. + * @param queryPoint */ - 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}. + * The default implementation is + *
return new QueryWrapperFilter(makeQuery(args).getQuery());
*/ - public abstract Filter makeFilter(SpatialArgs args); + public Filter makeFilter(SpatialArgs args) { + return new QueryWrapperFilter(makeQuery(args).getQuery()); + } + + /** + * Returns a ValueSource useful as a score. It is c/(d + c) where 'd' is the + * distance retrieved from {@link #makeDistanceValueSource(com.spatial4j.core.shape.Point)} + * and 'c' is one tenth the distance to the farthest edge from the center. + * Thus the scores will be as high as 1 for matching points in 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() { Index: lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java (revision ) +++ lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java (revision ) @@ -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); + } +} Index: lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java (revision ) @@ -45,6 +45,9 @@ */ public class SpatialArgsParser { + public static final String DIST_ERR_PCT = "distErrPct"; + public static final String DIST_ERR = "distErr"; + /** Writes a close approximation to the parsed input format. */ static String writeSpatialArgs(SpatialArgs args) { StringBuilder str = new StringBuilder(); @@ -90,8 +93,8 @@ body = v.substring(edx + 1).trim(); if (body.length() > 0) { Map aa = parseMap(body); - args.setDistErrPct(readDouble(aa.remove("distErrPct"))); - args.setDistErr(readDouble(aa.remove("distErr"))); + args.setDistErrPct(readDouble(aa.remove(DIST_ERR_PCT))); + args.setDistErr(readDouble(aa.remove(DIST_ERR))); if (!aa.isEmpty()) { throw new IllegalArgumentException("unused parameters: " + aa, null); } Index: lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java (revision 1384483) +++ lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java (revision ) @@ -32,14 +32,15 @@ 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 Index: lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java (revision ) @@ -18,6 +18,7 @@ */ 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; @@ -30,10 +31,8 @@ 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.NumericRangeQuery; import org.apache.lucene.search.Query; -import org.apache.lucene.search.QueryWrapperFilter; import org.apache.lucene.search.TermQuery; import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.query.SpatialArgs; @@ -113,30 +112,30 @@ // Query Builder //--------------------------------- + @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 ); + public ConstantScoreQuery makeQuery(SpatialArgs args) { + return new ConstantScoreQuery(makeSpatialQuery(args)); } - @Override - public Query makeQuery(SpatialArgs 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; } Index: lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java (revision 1384483) +++ lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestRecursivePrefixTreeStrategy.java (revision ) @@ -23,10 +23,6 @@ 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; @@ -196,17 +192,6 @@ assertTrue("has "+assertId,gotIds.contains(assertId)); } } - } - - 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. */ Index: lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeFieldCacheDistanceValueSource.java (revision ) @@ -17,6 +17,7 @@ * 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 @@ 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 = + public FunctionValues getValues(Map context, final AtomicReaderContext readerContext) throws IOException { + return new FunctionValues() { + private final ShapeFieldCache cache = - provider.getCache(readerContext.reader()); + 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); - return new FunctionValues() { @Override public float floatVal(int doc) { return (float) doubleVal(doc); @@ -73,7 +77,7 @@ } return v; } - return Double.NaN; // ?? maybe max? + return nullValue; } @Override @@ -90,16 +94,15 @@ 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(); } } Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java (revision ) @@ -17,7 +17,6 @@ * 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 @@ } @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 @@ } } } - Point point = args.getShape().getCenter(); - return new ShapeFieldCacheDistanceValueSource(point, calc, p); + + return new ShapeFieldCacheDistanceValueSource(ctx, p, queryPoint); } public SpatialPrefixTree getGrid() { Index: lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java (revision ) @@ -84,8 +84,9 @@ 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) { Index: lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java (revision 1384483) +++ lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java (revision ) @@ -24,10 +24,6 @@ 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 @@ 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); Index: lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java (revision ) +++ lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java (revision ) @@ -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; + } +} Index: lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/vector/TwoDoublesStrategy.java (revision ) @@ -30,7 +30,7 @@ import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.Filter; +import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.FilteredQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.NumericRangeQuery; @@ -94,32 +94,27 @@ } @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(), + public ConstantScoreQuery makeQuery(SpatialArgs args) { + if(! SpatialOperation.is( args.getOperation(), - SpatialOperation.Intersects, + 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() ); + 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)); - } + } - return new QueryWrapperFilter( makeQuery(args) ); - } - @Override - public Query makeQuery(SpatialArgs args) { + public Query makeQueryDistanceScore(SpatialArgs args) { // For starters, just limit the bbox Shape shape = args.getShape(); if (!(shape instanceof Rectangle || shape instanceof Circle)) { @@ -151,7 +146,7 @@ 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 +166,7 @@ valueSource = new CachingDoubleValueSource(valueSource); } else { - valueSource = makeValueSource(args); + valueSource = makeDistanceValueSource(shape.getCenter()); } Query spatialRankingQuery = new FunctionQuery(valueSource); BooleanQuery bq = new BooleanQuery(); Index: lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java (revision 1384483) +++ lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java (revision ) @@ -27,6 +27,11 @@ 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 @@ } } } + + 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); + } + } Index: lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java (revision 1384483) +++ lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java (revision ) @@ -30,7 +30,7 @@ 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 @@ 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 @@ */ @Override public String description() { - return "DistanceValueSource("+calculator+")"; + return "DistanceValueSource("+strategy+", "+from+")"; } - /** * Returns the FunctionValues used by the function query. */ @@ -71,6 +68,11 @@ 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 @@ @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 @@ }; } - /** - * 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 @@ 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(); } }