LUCENE-4208 makeQuery return ConstantScoreQuery, standardize makeDistanceValueSource behavior

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1385074 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2012-09-15 14:49:24 +00:00
parent ba5b6a4a71
commit 577e25c7ff
15 changed files with 374 additions and 177 deletions

View File

@ -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
* <pre>return new ConstantScoreQuery(makeFilter(args));</pre>
*/
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}.
* <p />
* If a subclasses implements
* {@link #makeQuery(org.apache.lucene.spatial.query.SpatialArgs)}
* then this method could be simply:
* <pre>return new QueryWrapperFilter(makeQuery(args).getQuery());</pre>
*/
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 <code>c/(d + c)</code> 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;

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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<Point> provider;
private final DistanceCalculator calculator;
private final SpatialContext ctx;
private final Point from;
public ShapeFieldCacheDistanceValueSource(Point from, DistanceCalculator calc, ShapeFieldCacheProvider<Point> provider) {
public ShapeFieldCacheDistanceValueSource(SpatialContext ctx, ShapeFieldCacheProvider<Point> 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<Point> cache =
provider.getCache(readerContext.reader());
public FunctionValues getValues(Map context, final AtomicReaderContext readerContext) throws IOException {
return new FunctionValues() {
private final ShapeFieldCache<Point> 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();
}
}

View File

@ -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 <code>true</code> 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();
}
}

View File

@ -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);

View File

@ -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<Object[]> parameters() {
List<Object[]> ctorArgs = new ArrayList<Object[]>();
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);
}
}

View File

@ -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);

View File

@ -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.
* <p />
* 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);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);