mirror of https://github.com/apache/lucene.git
SOLR-3120: spatial post-filter
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1242832 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e9380c5370
commit
e138de254f
|
@ -222,6 +222,10 @@ New Features
|
||||||
MaxFieldValueUpdateProcessorFactory
|
MaxFieldValueUpdateProcessorFactory
|
||||||
(hossman, janhoy)
|
(hossman, janhoy)
|
||||||
|
|
||||||
|
* SOLR-3120: Optional post filtering for spatial queries bbox and geofilt
|
||||||
|
for LatLonType. (yonik)
|
||||||
|
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -22,15 +22,16 @@ import org.apache.lucene.index.IndexReader;
|
||||||
import org.apache.lucene.index.AtomicReaderContext;
|
import org.apache.lucene.index.AtomicReaderContext;
|
||||||
import org.apache.lucene.queries.function.FunctionValues;
|
import org.apache.lucene.queries.function.FunctionValues;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
|
import org.apache.lucene.queries.function.ValueSourceScorer;
|
||||||
import org.apache.lucene.queries.function.valuesource.VectorValueSource;
|
import org.apache.lucene.queries.function.valuesource.VectorValueSource;
|
||||||
import org.apache.lucene.search.*;
|
import org.apache.lucene.search.*;
|
||||||
import org.apache.lucene.spatial.DistanceUtils;
|
import org.apache.lucene.spatial.DistanceUtils;
|
||||||
import org.apache.lucene.spatial.tier.InvalidGeoException;
|
import org.apache.lucene.spatial.tier.InvalidGeoException;
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.common.params.SolrParams;
|
||||||
import org.apache.solr.response.TextResponseWriter;
|
import org.apache.solr.response.TextResponseWriter;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.*;
|
||||||
import org.apache.solr.search.SpatialOptions;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -60,7 +61,7 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
|
||||||
IndexableField[] f = new IndexableField[(field.indexed() ? 2 : 0) + (field.stored() ? 1 : 0)];
|
IndexableField[] f = new IndexableField[(field.indexed() ? 2 : 0) + (field.stored() ? 1 : 0)];
|
||||||
if (field.indexed()) {
|
if (field.indexed()) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
double[] latLon = new double[0];
|
double[] latLon;
|
||||||
try {
|
try {
|
||||||
latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal);
|
latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal);
|
||||||
} catch (InvalidGeoException e) {
|
} catch (InvalidGeoException e) {
|
||||||
|
@ -194,8 +195,11 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
|
||||||
SchemaField latField = subField(options.field, LAT);
|
SchemaField latField = subField(options.field, LAT);
|
||||||
SchemaField lonField = subField(options.field, LONG);
|
SchemaField lonField = subField(options.field, LONG);
|
||||||
|
|
||||||
|
SpatialDistanceQuery spatial = new SpatialDistanceQuery();
|
||||||
|
|
||||||
|
|
||||||
if (options.bbox) {
|
if (options.bbox) {
|
||||||
BooleanQuery result = new BooleanQuery(); // only used if box==true
|
BooleanQuery result = new BooleanQuery();
|
||||||
|
|
||||||
Query latRange = latField.getType().getRangeQuery(parser, latField,
|
Query latRange = latField.getType().getRangeQuery(parser, latField,
|
||||||
String.valueOf(latMin),
|
String.valueOf(latMin),
|
||||||
|
@ -225,11 +229,10 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
|
||||||
result.add(lonRange, BooleanClause.Occur.MUST);
|
result.add(lonRange, BooleanClause.Occur.MUST);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
spatial.bboxQuery = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SpatialDistanceQuery spatial = new SpatialDistanceQuery();
|
|
||||||
spatial.origField = options.field.getName();
|
spatial.origField = options.field.getName();
|
||||||
spatial.latSource = latField.getType().getValueSource(latField, parser);
|
spatial.latSource = latField.getType().getValueSource(latField, parser);
|
||||||
spatial.lonSource = lonField.getType().getValueSource(lonField, parser);
|
spatial.lonSource = lonField.getType().getValueSource(lonField, parser);
|
||||||
|
@ -307,10 +310,12 @@ class LatLonValueSource extends VectorValueSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// TODO: recast as a value source that doesn't have to match all docs
|
// TODO: recast as a value source that doesn't have to match all docs
|
||||||
|
|
||||||
class SpatialDistanceQuery extends Query {
|
class SpatialDistanceQuery extends ExtendedQueryBase implements PostFilter {
|
||||||
String origField;
|
String origField;
|
||||||
ValueSource latSource;
|
ValueSource latSource;
|
||||||
ValueSource lonSource;
|
ValueSource lonSource;
|
||||||
|
@ -318,6 +323,7 @@ class SpatialDistanceQuery extends Query {
|
||||||
boolean lon2;
|
boolean lon2;
|
||||||
|
|
||||||
boolean calcDist; // actually calculate the distance with haversine
|
boolean calcDist; // actually calculate the distance with haversine
|
||||||
|
Query bboxQuery;
|
||||||
|
|
||||||
double latCenter;
|
double latCenter;
|
||||||
double lonCenter;
|
double lonCenter;
|
||||||
|
@ -327,12 +333,13 @@ class SpatialDistanceQuery extends Query {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Query rewrite(IndexReader reader) throws IOException {
|
public Query rewrite(IndexReader reader) throws IOException {
|
||||||
return this;
|
return bboxQuery != null ? bboxQuery.rewrite(reader) : this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void extractTerms(Set terms) {}
|
public void extractTerms(Set terms) {}
|
||||||
|
|
||||||
|
|
||||||
protected class SpatialWeight extends Weight {
|
protected class SpatialWeight extends Weight {
|
||||||
protected IndexSearcher searcher;
|
protected IndexSearcher searcher;
|
||||||
protected float queryNorm;
|
protected float queryNorm;
|
||||||
|
@ -385,7 +392,7 @@ class SpatialDistanceQuery extends Query {
|
||||||
int doc=-1;
|
int doc=-1;
|
||||||
final FunctionValues latVals;
|
final FunctionValues latVals;
|
||||||
final FunctionValues lonVals;
|
final FunctionValues lonVals;
|
||||||
final Bits liveDocs;
|
final Bits acceptDocs;
|
||||||
|
|
||||||
|
|
||||||
final double lonMin, lonMax, lon2Min, lon2Max, latMin, latMax;
|
final double lonMin, lonMax, lon2Min, lon2Max, latMin, latMax;
|
||||||
|
@ -407,7 +414,7 @@ class SpatialDistanceQuery extends Query {
|
||||||
this.qWeight = qWeight;
|
this.qWeight = qWeight;
|
||||||
this.reader = readerContext.reader();
|
this.reader = readerContext.reader();
|
||||||
this.maxDoc = reader.maxDoc();
|
this.maxDoc = reader.maxDoc();
|
||||||
this.liveDocs = acceptDocs;
|
this.acceptDocs = acceptDocs;
|
||||||
latVals = latSource.getValues(weight.latContext, readerContext);
|
latVals = latSource.getValues(weight.latContext, readerContext);
|
||||||
lonVals = lonSource.getValues(weight.lonContext, readerContext);
|
lonVals = lonSource.getValues(weight.lonContext, readerContext);
|
||||||
|
|
||||||
|
@ -485,7 +492,7 @@ class SpatialDistanceQuery extends Query {
|
||||||
if (doc>=maxDoc) {
|
if (doc>=maxDoc) {
|
||||||
return doc=NO_MORE_DOCS;
|
return doc=NO_MORE_DOCS;
|
||||||
}
|
}
|
||||||
if (liveDocs != null && !liveDocs.get(doc)) continue;
|
if (acceptDocs != null && !acceptDocs.get(doc)) continue;
|
||||||
if (!match()) continue;
|
if (!match()) continue;
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
@ -524,9 +531,44 @@ class SpatialDistanceQuery extends Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DelegatingCollector getFilterCollector(IndexSearcher searcher) {
|
||||||
|
try {
|
||||||
|
return new SpatialCollector(new SpatialWeight(searcher));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SpatialCollector extends DelegatingCollector {
|
||||||
|
final SpatialWeight weight;
|
||||||
|
SpatialScorer spatialScorer;
|
||||||
|
int maxdoc;
|
||||||
|
|
||||||
|
public SpatialCollector(SpatialWeight weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void collect(int doc) throws IOException {
|
||||||
|
spatialScorer.doc = doc;
|
||||||
|
if (spatialScorer.match()) delegate.collect(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNextReader(AtomicReaderContext context) throws IOException {
|
||||||
|
maxdoc = context.reader().maxDoc();
|
||||||
|
spatialScorer = new SpatialScorer(context, null, weight, 1.0f);
|
||||||
|
super.setNextReader(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Weight createWeight(IndexSearcher searcher) throws IOException {
|
public Weight createWeight(IndexSearcher searcher) throws IOException {
|
||||||
|
// if we were supposed to use bboxQuery, then we should have been rewritten using that query
|
||||||
|
assert bboxQuery == null;
|
||||||
return new SpatialWeight(searcher);
|
return new SpatialWeight(searcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +579,7 @@ class SpatialDistanceQuery extends Query {
|
||||||
{
|
{
|
||||||
float boost = getBoost();
|
float boost = getBoost();
|
||||||
return (boost!=1.0?"(":"") +
|
return (boost!=1.0?"(":"") +
|
||||||
"geofilt(latlonSource="+origField +"(" + latSource + "," + lonSource + ")"
|
(calcDist ? "geofilt" : "bbox") + "(latlonSource="+origField +"(" + latSource + "," + lonSource + ")"
|
||||||
+",latCenter="+latCenter+",lonCenter="+lonCenter
|
+",latCenter="+latCenter+",lonCenter="+lonCenter
|
||||||
+",dist=" + dist
|
+",dist=" + dist
|
||||||
+",latMin=" + latMin + ",latMax="+latMax
|
+",latMin=" + latMin + ",latMax="+latMax
|
||||||
|
@ -545,6 +587,7 @@ class SpatialDistanceQuery extends Query {
|
||||||
+",lon2Min=" + lon2Min + ",lon2Max" + lon2Max
|
+",lon2Min=" + lon2Min + ",lon2Max" + lon2Max
|
||||||
+",calcDist="+calcDist
|
+",calcDist="+calcDist
|
||||||
+",planetRadius="+planetRadius
|
+",planetRadius="+planetRadius
|
||||||
|
// + (bboxQuery == null ? "" : ",bboxQuery="+bboxQuery)
|
||||||
+")"
|
+")"
|
||||||
+ (boost==1.0 ? "" : ")^"+boost);
|
+ (boost==1.0 ? "" : ")^"+boost);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,9 @@ import java.io.IOException;
|
||||||
|
|
||||||
/** A simple delegating collector where one can set the delegate after creation */
|
/** A simple delegating collector where one can set the delegate after creation */
|
||||||
public class DelegatingCollector extends Collector {
|
public class DelegatingCollector extends Collector {
|
||||||
static int setLastDelegateCount; // for testing purposes only to determine the number of times a delegating collector chain was used
|
|
||||||
|
/* for internal testing purposes only to determine the number of times a delegating collector chain was used */
|
||||||
|
public static int setLastDelegateCount;
|
||||||
|
|
||||||
protected Collector delegate;
|
protected Collector delegate;
|
||||||
protected Scorer scorer;
|
protected Scorer scorer;
|
||||||
|
@ -74,3 +76,4 @@ public class DelegatingCollector extends Collector {
|
||||||
return delegate.acceptsDocsOutOfOrder();
|
return delegate.acceptsDocsOutOfOrder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,10 +149,29 @@ public class SpatialFilterTest extends SolrTestCaseJ4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
String method = exact ? "geofilt" : "bbox";
|
String method = exact ? "geofilt" : "bbox";
|
||||||
|
int postFilterCount = DelegatingCollector.setLastDelegateCount;
|
||||||
|
|
||||||
assertQ(req("fl", "id", "q","*:*", "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName +"}",
|
// throw in a random into the main query to prevent most cache hits
|
||||||
|
assertQ(req("fl", "id", "q","*:* OR foo_i:" + random.nextInt(100), "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName +"}",
|
||||||
"pt", pt, "d", String.valueOf(distance)),
|
"pt", pt, "d", String.valueOf(distance)),
|
||||||
tests);
|
tests);
|
||||||
|
assertEquals(postFilterCount, DelegatingCollector.setLastDelegateCount); // post filtering shouldn't be used
|
||||||
|
|
||||||
|
// try uncached
|
||||||
|
assertQ(req("fl", "id", "q","*:* OR foo_i:" + random.nextInt(100), "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName + " cache=false" + "}",
|
||||||
|
"pt", pt, "d", String.valueOf(distance)),
|
||||||
|
tests);
|
||||||
|
assertEquals(postFilterCount, DelegatingCollector.setLastDelegateCount); // post filtering shouldn't be used
|
||||||
|
|
||||||
|
// try post filtered for fields that support it
|
||||||
|
if (fieldName.endsWith("ll")) {
|
||||||
|
|
||||||
|
assertQ(req("fl", "id", "q","*:* OR foo_i:" + random.nextInt(100)+100, "rows", "1000", "fq", "{!"+method+" sfield=" +fieldName + " cache=false cost=150" + "}",
|
||||||
|
"pt", pt, "d", String.valueOf(distance)),
|
||||||
|
tests);
|
||||||
|
assertEquals(postFilterCount + 1, DelegatingCollector.setLastDelegateCount); // post filtering shouldn't be used
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue