LUCENE-7737: Remove spatial-extras dependency on queries module

This commit is contained in:
Alan Woodward 2017-06-13 09:41:23 +01:00
parent 1a278ae89b
commit 2f2e00ffe2
44 changed files with 816 additions and 779 deletions

View File

@ -24,10 +24,7 @@
<orderEntry type="library" scope="TEST" name="JUnit" level="project" />
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
<orderEntry type="module" module-name="lucene-core" />
<orderEntry type="module" module-name="queries" />
<orderEntry type="module" module-name="misc" />
<orderEntry type="module" module-name="spatial3d" />
<orderEntry type="module" module-name="backward-codecs" />
<orderEntry type="module" module-name="analysis-common" scope="TEST"/>
</component>
</module>
</module>

View File

@ -92,6 +92,11 @@ API Changes
* LUCENE-7723: DoubleValuesSource enforces implementation of equals() and
hashCode() (Alan Woodward)
* LUCENE-7737: The spatial-extras module no longer has a dependency on the
queries module. All uses of ValueSource are either replaced with core
DoubleValuesSource extensions, or with the new ShapeValuesSource and
ShapeValuesPredicate classes (Alan Woodward, David Smiley)
Bug Fixes
* LUCENE-7626: IndexWriter will no longer accept broken token offsets

View File

@ -21,16 +21,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.benchmark.byTask.utils.Config;
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.queries.function.FunctionScoreQuery;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.locationtech.spatial4j.shape.Shape;
/**
* Reads spatial data from the body field docs from an internally created {@link LineDocSource}.
@ -102,11 +100,8 @@ public class SpatialFileQueryMaker extends AbstractQueryMaker {
Query filterQuery = strategy.makeQuery(args);
if (score) {
//wrap with distance computing query
ValueSource valueSource = strategy.makeDistanceValueSource(shape.getCenter());
return new BooleanQuery.Builder()
.add(new FunctionQuery(valueSource), BooleanClause.Occur.MUST)//matches everything and provides score
.add(filterQuery, BooleanClause.Occur.FILTER)//filters (score isn't used)
.build();
DoubleValuesSource valueSource = strategy.makeDistanceValueSource(shape.getCenter());
return new FunctionScoreQuery(filterQuery, valueSource);
} else {
return filterQuery; // assume constant scoring
}

View File

@ -35,4 +35,25 @@ public abstract class DoubleValues {
*/
public abstract boolean advanceExact(int doc) throws IOException;
/**
* Wrap a DoubleValues instance, returning a default if the wrapped instance has no value
*/
public static DoubleValues withDefault(DoubleValues in, double missingValue) {
return new DoubleValues() {
boolean hasValue = false;
@Override
public double doubleValue() throws IOException {
return hasValue ? in.doubleValue() : missingValue;
}
@Override
public boolean advanceExact(int doc) throws IOException {
hasValue = in.advanceExact(doc);
return true;
}
};
}
}

View File

@ -64,7 +64,12 @@ public abstract class DoubleValuesSource {
* @return an Explanation for the value
* @throws IOException if an {@link IOException} occurs
*/
public abstract Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException;
public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
DoubleValues dv = getValues(ctx, DoubleValuesSource.constant(scoreExplanation.getValue()).getValues(ctx, null));
if (dv.advanceExact(docId))
return Explanation.match((float) dv.doubleValue(), this.toString());
return Explanation.noMatch(this.toString());
}
/**
* Create a sort field based on the value of this producer

View File

@ -258,6 +258,69 @@ public abstract class ValueSource {
}
public static ValueSource fromDoubleValuesSource(DoubleValuesSource in) {
return new FromDoubleValuesSource(in);
}
private static class FromDoubleValuesSource extends ValueSource {
final DoubleValuesSource in;
private FromDoubleValuesSource(DoubleValuesSource in) {
this.in = in;
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
Scorer scorer = (Scorer) context.get("scorer");
DoubleValues scores = scorer == null ? null : DoubleValuesSource.fromScorer(scorer);
DoubleValues inner = in.getValues(readerContext, scores);
return new FunctionValues() {
@Override
public String toString(int doc) throws IOException {
return in.toString();
}
@Override
public float floatVal(int doc) throws IOException {
if (inner.advanceExact(doc) == false)
return 0;
return (float) inner.doubleValue();
}
@Override
public double doubleVal(int doc) throws IOException {
if (inner.advanceExact(doc) == false)
return 0;
return inner.doubleValue();
}
@Override
public boolean exists(int doc) throws IOException {
return inner.advanceExact(doc);
}
};
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FromDoubleValuesSource that = (FromDoubleValuesSource) o;
return Objects.equals(in, that.in);
}
@Override
public int hashCode() {
return Objects.hash(in);
}
@Override
public String description() {
return in.toString();
}
}
//
// Sorting by function
//

View File

@ -31,7 +31,6 @@
<path id="classpath">
<path refid="base.classpath"/>
<path refid="spatialjar"/>
<pathelement path="${queries.jar}" />
<pathelement path="${spatial3d.jar}" />
</path>
@ -41,15 +40,12 @@
<pathelement path="src/test-files" />
</path>
<target name="compile-core" depends="jar-backward-codecs,jar-queries,jar-misc,jar-spatial3d,common.compile-core" />
<target name="compile-core" depends="jar-spatial3d,common.compile-core" />
<target name="javadocs" depends="javadocs-backward-codecs,javadocs-queries,javadocs-misc,javadocs-spatial3d,compile-core,check-javadocs-uptodate"
<target name="javadocs" depends="javadocs-spatial3d,compile-core,check-javadocs-uptodate"
unless="javadocs-uptodate-${name}">
<invoke-module-javadoc>
<links>
<link href="../backward-codecs"/>
<link href="../queries"/>
<link href="../misc"/>
<link href="../spatial3d"/>
</links>
</invoke-module-javadoc>

View File

@ -0,0 +1,41 @@
/*
* 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.
*/
package org.apache.lucene.spatial;
import java.io.IOException;
import org.locationtech.spatial4j.shape.Shape;
/**
* Iterator over {@link Shape} objects for an index segment
*/
public abstract class ShapeValues {
/**
* Advance the iterator to the given document
* @param doc the document to advance to
* @return {@code true} if there is a value for this document
*/
public abstract boolean advanceExact(int doc) throws IOException;
/**
* Returns a {@link Shape} for the current document
*/
public abstract Shape value() throws IOException;
}

View File

@ -0,0 +1,34 @@
/*
* 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.
*/
package org.apache.lucene.spatial;
import java.io.IOException;
import org.apache.lucene.index.LeafReaderContext;
/**
* Produces {@link ShapeValues} per-segment
*/
public abstract class ShapeValuesSource {
/**
* Get a {@link ShapeValues} instance for the given leaf reader context
*/
public abstract ShapeValues getValues(LeafReaderContext ctx) throws IOException;
}

View File

@ -16,15 +16,15 @@
*/
package org.apache.lucene.spatial;
import org.apache.lucene.document.Field;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.ReciprocalDoubleValuesSource;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.document.Field;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.ReciprocalFloatFunction;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.query.SpatialArgs;
/**
* The SpatialStrategy encapsulates an approach to indexing and searching based
@ -103,7 +103,7 @@ public abstract class SpatialStrategy {
* See {@link #makeDistanceValueSource(org.locationtech.spatial4j.shape.Point, double)} called with
* a multiplier of 1.0 (i.e. units of degrees).
*/
public ValueSource makeDistanceValueSource(Point queryPoint) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint) {
return makeDistanceValueSource(queryPoint, 1.0);
}
@ -113,7 +113,7 @@ public abstract class SpatialStrategy {
* then the closest one is chosen. The result is multiplied by {@code multiplier}, which
* conveniently is used to get the desired units.
*/
public abstract ValueSource makeDistanceValueSource(Point queryPoint, double multiplier);
public abstract DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier);
/**
* Make a Query based principally on {@link org.apache.lucene.spatial.query.SpatialOperation}
@ -133,13 +133,14 @@ public abstract class SpatialStrategy {
* 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) {
public final DoubleValuesSource 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(), 1.0), 1f, c, c);
DoubleValuesSource distance = makeDistanceValueSource(queryShape.getCenter(), 1.0);
return new ReciprocalDoubleValuesSource(c, distance);
}
@Override

View File

@ -18,9 +18,8 @@ package org.apache.lucene.spatial.bbox;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.shape.Rectangle;
/**
@ -79,7 +78,7 @@ public class BBoxOverlapRatioValueSource extends BBoxSimilarityValueSource {
* @param queryTargetProportion see class javadocs. Between 0 and 1.
* @param minSideLength see class javadocs. 0.0 will effectively disable.
*/
public BBoxOverlapRatioValueSource(ValueSource rectValueSource, boolean isGeo, Rectangle queryExtent,
public BBoxOverlapRatioValueSource(ShapeValuesSource rectValueSource, boolean isGeo, Rectangle queryExtent,
double queryTargetProportion, double minSideLength) {
super(rectValueSource);
this.isGeo = isGeo;
@ -94,7 +93,7 @@ public class BBoxOverlapRatioValueSource extends BBoxSimilarityValueSource {
/** Construct with 75% weighting towards target (roughly GeoPortal's default), geo degrees assumed, no
* minimum side length. */
public BBoxOverlapRatioValueSource(ValueSource rectValueSource, Rectangle queryExtent) {
public BBoxOverlapRatioValueSource(ShapeValuesSource rectValueSource, Rectangle queryExtent) {
this(rectValueSource, true, queryExtent, 0.25, 0.0);
}

View File

@ -17,78 +17,58 @@
package org.apache.lucene.spatial.bbox;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.shape.Rectangle;
/**
* A base class for calculating a spatial relevance rank per document from a provided
* {@link ValueSource} in which {@link FunctionValues#objectVal(int)} returns a {@link
* org.locationtech.spatial4j.shape.Rectangle}.
* {@link ShapeValuesSource} returning a {@link
* org.locationtech.spatial4j.shape.Rectangle} per-document.
* <p>
* Implementers: remember to implement equals and hashCode if you have
* fields!
*
* @lucene.experimental
*/
public abstract class BBoxSimilarityValueSource extends ValueSource {
public abstract class BBoxSimilarityValueSource extends DoubleValuesSource {
private final ValueSource bboxValueSource;
private final ShapeValuesSource bboxValueSource;
public BBoxSimilarityValueSource(ValueSource bboxValueSource) {
public BBoxSimilarityValueSource(ShapeValuesSource bboxValueSource) {
this.bboxValueSource = bboxValueSource;
}
@Override
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
bboxValueSource.createWeight(context, searcher);
public String toString() {
return getClass().getSimpleName()+"(" + bboxValueSource.toString() + "," + similarityDescription() + ")";
}
@Override
public String description() {
return getClass().getSimpleName()+"(" + bboxValueSource.description() + "," + similarityDescription() + ")";
}
/** A comma-separated list of configurable items of the subclass to put into {@link #description()}. */
/** A comma-separated list of configurable items of the subclass to put into {@link #toString()}. */
protected abstract String similarityDescription();
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
final FunctionValues shapeValues = bboxValueSource.getValues(context, readerContext);
return new DoubleDocValues(this) {
final ShapeValues shapeValues = bboxValueSource.getValues(readerContext);
return DoubleValues.withDefault(new DoubleValues() {
@Override
public double doubleVal(int doc) throws IOException {
//? limit to Rect or call getBoundingBox()? latter would encourage bad practice
final Rectangle rect = (Rectangle) shapeValues.objectVal(doc);
return rect==null ? 0 : score(rect, null);
public double doubleValue() throws IOException {
return score((Rectangle) shapeValues.value(), null);
}
@Override
public boolean exists(int doc) throws IOException {
return shapeValues.exists(doc);
public boolean advanceExact(int doc) throws IOException {
return shapeValues.advanceExact(doc);
}
}, 0);
@Override
public Explanation explain(int doc) throws IOException {
final Rectangle rect = (Rectangle) shapeValues.objectVal(doc);
if (rect == null) {
return Explanation.noMatch("no rect");
}
AtomicReference<Explanation> explanation = new AtomicReference<>();
score(rect, explanation);
return explanation.get();
}
};
}
/**
@ -115,4 +95,23 @@ public abstract class BBoxSimilarityValueSource extends ValueSource {
public int hashCode() {
return bboxValueSource.hashCode();
}
@Override
public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
DoubleValues dv = getValues(ctx, DoubleValuesSource.constant(scoreExplanation.getValue()).getValues(ctx, null));
if (dv.advanceExact(docId)) {
AtomicReference<Explanation> explanation = new AtomicReference<>();
final ShapeValues shapeValues = bboxValueSource.getValues(ctx);
if (shapeValues.advanceExact(docId)) {
score((Rectangle) shapeValues.value(), explanation);
return explanation.get();
}
}
return Explanation.noMatch(this.toString());
}
@Override
public boolean needsScores() {
return false;
}
}

View File

@ -25,12 +25,13 @@ import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term;
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.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
@ -211,23 +212,21 @@ public class BBoxStrategy extends SpatialStrategy {
//---------------------------------
/**
* Provides access to each rectangle per document as a ValueSource in which
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
* Shape}.
* Provides access to each rectangle per document as a {@link ShapeValuesSource}
*/ //TODO raise to SpatialStrategy
public ValueSource makeShapeValueSource() {
public ShapeValuesSource makeShapeValueSource() {
return new BBoxValueSource(this);
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
//TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
}
/** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a
* convenience method. */
public ValueSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
public DoubleValuesSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
return new BBoxOverlapRatioValueSource(
makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0);
}

View File

@ -17,24 +17,22 @@
package org.apache.lucene.spatial.bbox;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/**
* A ValueSource in which the indexed Rectangle is returned from
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
* A ShapeValuesSource returning a Rectangle from each document derived from four numeric fields
*
* @lucene.internal
*/
class BBoxValueSource extends ValueSource {
class BBoxValueSource extends ShapeValuesSource {
private final BBoxStrategy strategy;
@ -43,12 +41,12 @@ class BBoxValueSource extends ValueSource {
}
@Override
public String description() {
public String toString() {
return "bboxShape(" + strategy.getFieldName() + ")";
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
LeafReader reader = readerContext.reader();
final NumericDocValues minX = DocValues.getNumeric(reader, strategy.field_minX);
final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY);
@ -58,61 +56,23 @@ class BBoxValueSource extends ValueSource {
//reused
final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
return new FunctionValues() {
private int lastDocID = -1;
return new ShapeValues() {
private double getDocValue(NumericDocValues values, int doc) throws IOException {
int curDocID = values.docID();
if (doc > curDocID) {
curDocID = values.advance(doc);
}
if (doc == curDocID) {
return Double.longBitsToDouble(values.longValue());
} else {
return 0.0;
}
@Override
public boolean advanceExact(int doc) throws IOException {
return minX.advanceExact(doc) && minY.advanceExact(doc) && maxX.advanceExact(doc) && maxY.advanceExact(doc);
}
@Override
public Object objectVal(int doc) throws IOException {
if (doc < lastDocID) {
throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + doc);
}
lastDocID = doc;
double minXValue = getDocValue(minX, doc);
if (minX.docID() != doc) {
return null;
} else {
double minYValue = getDocValue(minY, doc);
double maxXValue = getDocValue(maxX, doc);
double maxYValue = getDocValue(maxY, doc);
rect.reset(minXValue, maxXValue, minYValue, maxYValue);
return rect;
}
public Shape value() throws IOException {
double minXValue = Double.longBitsToDouble(minX.longValue());
double minYValue = Double.longBitsToDouble(minY.longValue());
double maxXValue = Double.longBitsToDouble(maxX.longValue());
double maxYValue = Double.longBitsToDouble(maxY.longValue());
rect.reset(minXValue, maxXValue, minYValue, maxYValue);
return rect;
}
@Override
public String strVal(int doc) throws IOException {//TODO support WKT output once Spatial4j does
Object v = objectVal(doc);
return v == null ? null : v.toString();
}
@Override
public boolean exists(int doc) throws IOException {
getDocValue(minX, doc);
return minX.docID() == doc;
}
@Override
public Explanation explain(int doc) throws IOException {
return Explanation.match(Float.NaN, toString(doc));
}
@Override
public String toString(int doc) throws IOException {
return description() + '=' + strVal(doc);
}
};
}

View File

@ -20,10 +20,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.document.Field;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
@ -32,7 +30,9 @@ import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial.util.ShapePredicateValueSource;
import org.apache.lucene.spatial.util.ShapeValuesPredicate;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
/**
* A composite {@link SpatialStrategy} based on {@link RecursivePrefixTreeStrategy} (RPT) and
@ -86,7 +86,7 @@ public class CompositeSpatialStrategy extends SpatialStrategy {
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
//TODO consider indexing center-point in DV? Guarantee contained by the shape, which could then be used for
// other purposes like faster WITHIN predicate?
throw new UnsupportedOperationException();
@ -108,8 +108,8 @@ public class CompositeSpatialStrategy extends SpatialStrategy {
throw new UnsupportedSpatialOperation(pred);
}
final ShapePredicateValueSource predicateValueSource =
new ShapePredicateValueSource(geometryStrategy.makeShapeValueSource(), pred, args.getShape());
final ShapeValuesPredicate predicateValueSource =
new ShapeValuesPredicate(geometryStrategy.makeShapeValueSource(), pred, args.getShape());
//System.out.println("PredOpt: " + optimizePredicates);
if (pred == SpatialOperation.Intersects && optimizePredicates) {
// We have a smart Intersects impl

View File

@ -17,12 +17,9 @@
package org.apache.lucene.spatial.composite;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.IndexSearcher;
@ -30,19 +27,20 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;
import org.apache.lucene.spatial.util.ShapeValuesPredicate;
/**
* A Query that considers an "indexQuery" to have approximate results, and a follow-on
* {@link ValueSource}/{@link FunctionValues#boolVal(int)} is called to verify each hit
* from {@link TwoPhaseIterator#matches()}.
* ShapeValuesSource is called to verify each hit from {@link TwoPhaseIterator#matches()}.
*
* @lucene.experimental
*/
public class CompositeVerifyQuery extends Query {
final Query indexQuery;//approximation (matches more than needed)
final ValueSource predicateValueSource;//we call boolVal(doc)
public CompositeVerifyQuery(Query indexQuery, ValueSource predicateValueSource) {
private final Query indexQuery;//approximation (matches more than needed)
private final ShapeValuesPredicate predicateValueSource;
public CompositeVerifyQuery(Query indexQuery, ShapeValuesPredicate predicateValueSource) {
this.indexQuery = indexQuery;
this.predicateValueSource = predicateValueSource;
}
@ -84,7 +82,6 @@ public class CompositeVerifyQuery extends Query {
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
final Weight indexQueryWeight = indexQuery.createWeight(searcher, false, boost);//scores aren't unsupported
final Map valueSourceContext = ValueSource.newContext(searcher);
return new ConstantScoreWeight(this, boost) {
@ -96,21 +93,8 @@ public class CompositeVerifyQuery extends Query {
return null;
}
final FunctionValues predFuncValues = predicateValueSource.getValues(valueSourceContext, context);
final TwoPhaseIterator twoPhaseIterator = new TwoPhaseIterator(indexQueryScorer.iterator()) {
@Override
public boolean matches() throws IOException {
return predFuncValues.boolVal(indexQueryScorer.docID());
}
@Override
public float matchCost() {
return 100; // TODO: use cost of predFuncValues.boolVal()
}
};
return new ConstantScoreScorer(this, score(), twoPhaseIterator);
final TwoPhaseIterator predFuncValues = predicateValueSource.iterator(context, indexQueryScorer.iterator());
return new ConstantScoreScorer(this, score(), predFuncValues);
}
};
}

View File

@ -17,11 +17,8 @@
package org.apache.lucene.spatial.composite;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSet;
@ -34,6 +31,7 @@ import org.apache.lucene.search.Weight;
import org.apache.lucene.spatial.prefix.AbstractVisitingPrefixTreeQuery;
import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.util.ShapeValuesPredicate;
import org.apache.lucene.util.DocIdSetBuilder;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
@ -41,17 +39,17 @@ import org.locationtech.spatial4j.shape.SpatialRelation;
/**
* A spatial Intersects predicate that distinguishes an approximated match from an exact match based on which cells
* are within the query shape. It exposes a {@link TwoPhaseIterator} that will verify a match with a provided
* predicate in the form of a {@link ValueSource} by calling {@link FunctionValues#boolVal(int)}.
* predicate in the form of an ShapeValuesPredicate.
*
* @lucene.internal
*/
public class IntersectsRPTVerifyQuery extends Query {
private final IntersectsDifferentiatingQuery intersectsDiffQuery;
private final ValueSource predicateValueSource; // we call FunctionValues.boolVal(doc)
private final ShapeValuesPredicate predicateValueSource;
public IntersectsRPTVerifyQuery(Shape queryShape, String fieldName, SpatialPrefixTree grid, int detailLevel,
int prefixGridScanLevel, ValueSource predicateValueSource) {
int prefixGridScanLevel, ShapeValuesPredicate predicateValueSource) {
this.predicateValueSource = predicateValueSource;
this.intersectsDiffQuery = new IntersectsDifferentiatingQuery(queryShape, fieldName, grid, detailLevel,
prefixGridScanLevel);
@ -83,7 +81,6 @@ public class IntersectsRPTVerifyQuery extends Query {
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
final Map valueSourceContext = ValueSource.newContext(searcher);
return new ConstantScoreWeight(this, boost) {
@Override
@ -110,9 +107,10 @@ public class IntersectsRPTVerifyQuery extends Query {
exactIterator = null;
}
final FunctionValues predFuncValues = predicateValueSource.getValues(valueSourceContext, context);
final TwoPhaseIterator twoPhaseIterator = new TwoPhaseIterator(approxDISI) {
final TwoPhaseIterator predFuncValues = predicateValueSource.iterator(context, approxDISI);
@Override
public boolean matches() throws IOException {
final int doc = approxDISI.docID();
@ -124,13 +122,12 @@ public class IntersectsRPTVerifyQuery extends Query {
return true;
}
}
return predFuncValues.boolVal(doc);
return predFuncValues.matches();
}
@Override
public float matchCost() {
return 100; // TODO: use cost of exactIterator.advance() and predFuncValues.boolVal()
return 100; // TODO: use cost of exactIterator.advance() and predFuncValues.cost()
}
};

View File

@ -23,7 +23,7 @@ import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.lucene.index.IndexReaderContext;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree;
import org.apache.lucene.util.Bits;
@ -76,7 +76,7 @@ public class NumberRangePrefixTreeStrategy extends RecursivePrefixTreeStrategy {
/** Unsupported. */
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
throw new UnsupportedOperationException();
}

View File

@ -25,7 +25,7 @@ import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReaderContext;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
@ -180,7 +180,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() );
if( p == null ) {
synchronized (this) {//double checked locking idiom is okay since provider is threadsafe

View File

@ -22,29 +22,27 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.DistanceToShapeValueSource;
import org.apache.lucene.spatial.util.ShapePredicateValueSource;
import org.apache.lucene.spatial.util.ShapeValuesPredicate;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.io.BinaryCodec;
import org.locationtech.spatial4j.shape.Point;
@ -100,7 +98,7 @@ public class SerializedDVStrategy extends SpatialStrategy {
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
//TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
}
@ -111,18 +109,15 @@ public class SerializedDVStrategy extends SpatialStrategy {
*/
@Override
public Query makeQuery(SpatialArgs args) {
ValueSource shapeValueSource = makeShapeValueSource();
ShapePredicateValueSource predicateValueSource = new ShapePredicateValueSource(
shapeValueSource, args.getOperation(), args.getShape());
ShapeValuesSource shapeValueSource = makeShapeValueSource();
ShapeValuesPredicate predicateValueSource = new ShapeValuesPredicate(shapeValueSource, args.getOperation(), args.getShape());
return new PredicateValueSourceQuery(predicateValueSource);
}
/**
* Provides access to each shape per document as a ValueSource in which
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
* Shape}.
* Provides access to each shape per document
*/ //TODO raise to SpatialStrategy
public ValueSource makeShapeValueSource() {
public ShapeValuesSource makeShapeValueSource() {
return new ShapeDocValueSource(getFieldName(), ctx.getBinaryCodec());
}
@ -130,9 +125,9 @@ public class SerializedDVStrategy extends SpatialStrategy {
* by {@link TwoPhaseIterator}.
*/
static class PredicateValueSourceQuery extends Query {
private final ValueSource predicateValueSource;//we call boolVal(doc)
private final ShapeValuesPredicate predicateValueSource;
public PredicateValueSourceQuery(ValueSource predicateValueSource) {
public PredicateValueSourceQuery(ShapeValuesPredicate predicateValueSource) {
this.predicateValueSource = predicateValueSource;
}
@ -142,21 +137,8 @@ public class SerializedDVStrategy extends SpatialStrategy {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
DocIdSetIterator approximation = DocIdSetIterator.all(context.reader().maxDoc());
final FunctionValues predFuncValues = predicateValueSource.getValues(null, context);
return new ConstantScoreScorer(this, score(), new TwoPhaseIterator(approximation) {
@Override
public boolean matches() throws IOException {
final int docID = approximation.docID();
return predFuncValues.boolVal(docID);
}
@Override
public float matchCost() {
// TODO: what is the cost of the predicateValueSource
return 100f;
}
});
TwoPhaseIterator it = predicateValueSource.iterator(context, approximation);
return new ConstantScoreScorer(this, score(), it);
}
};
}
@ -184,7 +166,7 @@ public class SerializedDVStrategy extends SpatialStrategy {
* Implements a ValueSource by deserializing a Shape in from BinaryDocValues using BinaryCodec.
* @see #makeShapeValueSource()
*/
static class ShapeDocValueSource extends ValueSource {
static class ShapeDocValueSource extends ShapeValuesSource {
private final String fieldName;
private final BinaryCodec binaryCodec;//spatial4j
@ -195,65 +177,21 @@ public class SerializedDVStrategy extends SpatialStrategy {
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
final BinaryDocValues docValues = readerContext.reader().getBinaryDocValues(fieldName);
return new FunctionValues() {
int bytesRefDoc = -1;
BytesRefBuilder bytesRef = new BytesRefBuilder();
boolean fillBytes(int doc) throws IOException {
if (bytesRefDoc != doc) {
if (docValues.docID() < doc) {
docValues.advance(doc);
}
if (docValues.docID() == doc) {
bytesRef.copyBytes(docValues.binaryValue());
} else {
bytesRef.clear();
}
bytesRefDoc = doc;
}
return bytesRef.length() != 0;
return new ShapeValues() {
@Override
public boolean advanceExact(int doc) throws IOException {
return docValues.advanceExact(doc);
}
@Override
public boolean exists(int doc) throws IOException {
return fillBytes(doc);
}
@Override
public boolean bytesVal(int doc, BytesRefBuilder target) throws IOException {
target.clear();
if (fillBytes(doc)) {
target.copyBytes(bytesRef);
return true;
} else {
return false;
}
}
@Override
public Object objectVal(int docId) throws IOException {
if (!fillBytes(docId))
return null;
DataInputStream dataInput = new DataInputStream(
new ByteArrayInputStream(bytesRef.bytes(), 0, bytesRef.length()));
try {
return binaryCodec.readShape(dataInput);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Explanation explain(int doc) throws IOException {
return Explanation.match(Float.NaN, toString(doc));
}
@Override
public String toString(int doc) throws IOException {
return description() + "=" + objectVal(doc);//TODO truncate?
public Shape value() throws IOException {
BytesRef bytesRef = docValues.binaryValue();
DataInputStream dataInput
= new DataInputStream(new ByteArrayInputStream(bytesRef.bytes, bytesRef.offset, bytesRef.length));
return binaryCodec.readShape(dataInput);
}
};
@ -278,7 +216,7 @@ public class SerializedDVStrategy extends SpatialStrategy {
}
@Override
public String description() {
public String toString() {
return "shapeDocVal(" + fieldName + ")";
}
}//ShapeDocValueSource

View File

@ -16,64 +16,73 @@
*/
package org.apache.lucene.spatial.util;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Explanation;
/**
* Caches the doubleVal of another value source in a HashMap
* so that it is computed only once.
* @lucene.internal
*/
public class CachingDoubleValueSource extends ValueSource {
public class CachingDoubleValueSource extends DoubleValuesSource {
final ValueSource source;
final DoubleValuesSource source;
final Map<Integer, Double> cache;
public CachingDoubleValueSource( ValueSource source )
{
public CachingDoubleValueSource(DoubleValuesSource source) {
this.source = source;
cache = new HashMap<>();
}
@Override
public String description() {
return "Cached["+source.description()+"]";
public String toString() {
return "Cached["+source.toString()+"]";
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
final int base = readerContext.docBase;
final FunctionValues vals = source.getValues(context,readerContext);
return new FunctionValues() {
final DoubleValues vals = source.getValues(readerContext, scores);
return new DoubleValues() {
@Override
public double doubleVal(int doc) throws IOException {
Integer key = Integer.valueOf( base+doc );
Double v = cache.get( key );
if( v == null ) {
v = Double.valueOf( vals.doubleVal(doc) );
cache.put( key, v );
public double doubleValue() throws IOException {
int key = base + doc;
Double v = cache.get(key);
if (v == null) {
v = vals.doubleValue();
cache.put(key, v);
}
return v.doubleValue();
return v;
}
@Override
public float floatVal(int doc) throws IOException {
return (float)doubleVal(doc);
public boolean advanceExact(int doc) throws IOException {
this.doc = doc;
return vals.advanceExact(doc);
}
@Override
public String toString(int doc) throws IOException {
return doubleVal(doc)+"";
}
int doc = -1;
};
}
@Override
public boolean needsScores() {
return false;
}
@Override
public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
return source.explain(ctx, docId, scoreExplanation);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -17,81 +17,67 @@
package org.apache.lucene.spatial.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceCalculator;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
/**
* The distance from a provided Point to a Point retrieved from a ValueSource via
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}. The distance
* The distance from a provided Point to a Point retrieved from an ShapeValuesSource. The distance
* is calculated via a {@link org.locationtech.spatial4j.distance.DistanceCalculator}.
*
* @lucene.experimental
*/
public class DistanceToShapeValueSource extends ValueSource {
private final ValueSource shapeValueSource;
public class DistanceToShapeValueSource extends DoubleValuesSource {
private final ShapeValuesSource shapeValueSource;
private final Point queryPoint;
private final double multiplier;
private final DistanceCalculator distCalc;
//TODO if FunctionValues returns NaN; will things be ok?
private final double nullValue;//computed
//TODO if DoubleValues returns NaN; will things be ok?
private final double nullValue;
public DistanceToShapeValueSource(ValueSource shapeValueSource, Point queryPoint,
public DistanceToShapeValueSource(ShapeValuesSource shapeValueSource, Point queryPoint,
double multiplier, SpatialContext ctx) {
this.shapeValueSource = shapeValueSource;
this.queryPoint = queryPoint;
this.multiplier = multiplier;
this.distCalc = ctx.getDistCalc();
this.nullValue =
(ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
this.nullValue = (ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
}
@Override
public String description() {
return "distance(" + queryPoint + " to " + shapeValueSource.description() + ")*" + multiplier + ")";
public String toString() {
return "distance(" + queryPoint + " to " + shapeValueSource.toString() + ")*" + multiplier + ")";
}
@Override
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
shapeValueSource.createWeight(context, searcher);
}
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
final FunctionValues shapeValues = shapeValueSource.getValues(context, readerContext);
final ShapeValues shapeValues = shapeValueSource.getValues(readerContext);
return new DoubleDocValues(this) {
return DoubleValues.withDefault(new DoubleValues() {
@Override
public double doubleVal(int doc) throws IOException {
Shape shape = (Shape) shapeValues.objectVal(doc);
if (shape == null || shape.isEmpty())
return nullValue;
Point pt = shape.getCenter();
return distCalc.distance(queryPoint, pt) * multiplier;
public double doubleValue() throws IOException {
return distCalc.distance(queryPoint, shapeValues.value().getCenter()) * multiplier;
}
@Override
public Explanation explain(int doc) throws IOException {
Explanation exp = super.explain(doc);
List<Explanation> details = new ArrayList<>(Arrays.asList(exp.getDetails()));
details.add(shapeValues.explain(doc));
return Explanation.match(exp.getValue(), exp.getDescription(), details);
public boolean advanceExact(int doc) throws IOException {
return shapeValues.advanceExact(doc);
}
};
}, nullValue);
}
@Override
public boolean needsScores() {
return false;
}
@Override

View File

@ -0,0 +1,96 @@
/*
* 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.
*/
package org.apache.lucene.spatial.util;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Explanation;
/**
* Transforms a DoubleValuesSource using the formula v = k / (v + k)
*/
public class ReciprocalDoubleValuesSource extends DoubleValuesSource {
private final double distToEdge;
private final DoubleValuesSource input;
/**
* Creates a ReciprocalDoubleValuesSource
* @param distToEdge the value k in v = k / (v + k)
* @param input the input DoubleValuesSource to transform
*/
public ReciprocalDoubleValuesSource(double distToEdge, DoubleValuesSource input) {
this.distToEdge = distToEdge;
this.input = input;
}
@Override
public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
DoubleValues in = input.getValues(ctx, scores);
return new DoubleValues() {
@Override
public double doubleValue() throws IOException {
return recip(in.doubleValue());
}
@Override
public boolean advanceExact(int doc) throws IOException {
return in.advanceExact(doc);
}
};
}
private double recip(double in) {
return distToEdge / (in + distToEdge);
}
@Override
public boolean needsScores() {
return input.needsScores();
}
@Override
public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
Explanation expl = input.explain(ctx, docId, scoreExplanation);
return Explanation.match((float)recip(expl.getValue()),
distToEdge + " / (v + " + distToEdge + "), computed from:", expl);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReciprocalDoubleValuesSource that = (ReciprocalDoubleValuesSource) o;
return Double.compare(that.distToEdge, distToEdge) == 0 &&
Objects.equals(input, that.input);
}
@Override
public int hashCode() {
return Objects.hash(distToEdge, input);
}
@Override
public String toString() {
return "recip(" + distToEdge + ", " + input.toString() + ")";
}
}

View File

@ -17,36 +17,29 @@
package org.apache.lucene.spatial.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
/**
* The area of a Shape retrieved from a ValueSource via
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
* The area of a Shape retrieved from an ShapeValuesSource
*
* @see Shape#getArea(org.locationtech.spatial4j.context.SpatialContext)
*
* @lucene.experimental
*/
public class ShapeAreaValueSource extends ValueSource {
private final ValueSource shapeValueSource;
public class ShapeAreaValueSource extends DoubleValuesSource {
private final ShapeValuesSource shapeValueSource;
private final SpatialContext ctx;//not part of identity; should be associated with shapeValueSource indirectly
private final boolean geoArea;
private double multiplier;
public ShapeAreaValueSource(ValueSource shapeValueSource, SpatialContext ctx, boolean geoArea, double multiplier) {
public ShapeAreaValueSource(ShapeValuesSource shapeValueSource, SpatialContext ctx, boolean geoArea, double multiplier) {
this.shapeValueSource = shapeValueSource;
this.ctx = ctx;
this.geoArea = geoArea;
@ -54,43 +47,29 @@ public class ShapeAreaValueSource extends ValueSource {
}
@Override
public String description() {
return "area(" + shapeValueSource.description() + ",geo=" + geoArea + ")";
public String toString() {
return "area(" + shapeValueSource.toString() + ",geo=" + geoArea + ")";
}
@Override
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
shapeValueSource.createWeight(context, searcher);
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
final ShapeValues shapeValues = shapeValueSource.getValues(readerContext);
return DoubleValues.withDefault(new DoubleValues() {
@Override
public double doubleValue() throws IOException {
return shapeValues.value().getArea(geoArea ? ctx : null) * multiplier;
}
@Override
public boolean advanceExact(int doc) throws IOException {
return shapeValues.advanceExact(doc);
}
}, 0);
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
final FunctionValues shapeValues = shapeValueSource.getValues(context, readerContext);
return new DoubleDocValues(this) {
@Override
public double doubleVal(int doc) throws IOException {
Shape shape = (Shape) shapeValues.objectVal(doc);
if (shape == null || shape.isEmpty())
return 0;//or NaN?
//This part of Spatial4j API is kinda weird. Passing null means 2D area, otherwise geo
// assuming ctx.isGeo()
return shape.getArea( geoArea ? ctx : null ) * multiplier;
}
@Override
public boolean exists(int doc) throws IOException {
return shapeValues.exists(doc);
}
@Override
public Explanation explain(int doc) throws IOException {
Explanation exp = super.explain(doc);
List<Explanation> details = new ArrayList<>(Arrays.asList(exp.getDetails()));
details.add(shapeValues.explain(doc));
return Explanation.match(exp.getValue(), exp.getDescription(), details);
}
};
public boolean needsScores() {
return false;
}
@Override

View File

@ -16,26 +16,25 @@
*/
package org.apache.lucene.spatial.util;
import java.io.IOException;
import java.util.List;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceCalculator;
import org.locationtech.spatial4j.shape.Point;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* An implementation of the Lucene ValueSource that returns the spatial distance
* A DoubleValuesSource that returns the spatial distance
* between an input point and a document's points in
* {@link ShapeFieldCacheProvider}. The shortest distance is returned if a
* document has more than one point.
*
* @lucene.internal
*/
public class ShapeFieldCacheDistanceValueSource extends ValueSource {
public class ShapeFieldCacheDistanceValueSource extends DoubleValuesSource {
private final SpatialContext ctx;
private final Point from;
@ -51,43 +50,43 @@ public class ShapeFieldCacheDistanceValueSource extends ValueSource {
}
@Override
public String description() {
public String toString() {
return getClass().getSimpleName()+"("+provider+", "+from+")";
}
@Override
public FunctionValues getValues(Map context, final LeafReaderContext readerContext) throws IOException {
return new FunctionValues() {
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
final double nullValue = (ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
return DoubleValues.withDefault(new DoubleValues() {
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 * multiplier : Double.MAX_VALUE);
private List<Point> currentVals;
@Override
public float floatVal(int doc) {
return (float) doubleVal(doc);
}
@Override
public double doubleVal(int doc) {
List<Point> vals = cache.getShapes( doc );
if( vals != null ) {
double v = calculator.distance(from, vals.get(0));
for( int i=1; i<vals.size(); i++ ) {
v = Math.min(v, calculator.distance(from, vals.get(i)));
}
return v * multiplier;
public double doubleValue() throws IOException {
double v = calculator.distance(from, currentVals.get(0));
for (int i = 1; i < currentVals.size(); i++) {
v = Math.min(v, calculator.distance(from, currentVals.get(i)));
}
return nullValue;
return v * multiplier;
}
@Override
public String toString(int doc) {
return description() + "=" + floatVal(doc);
public boolean advanceExact(int doc) throws IOException {
currentVals = cache.getShapes(doc);
return currentVals != null;
}
};
}, nullValue);
}
@Override
public boolean needsScores() {
return false;
}
@Override

View File

@ -16,30 +16,29 @@
*/
package org.apache.lucene.spatial.util;
import org.locationtech.spatial4j.shape.Shape;
import java.io.IOException;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.BoolDocValues;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.query.SpatialOperation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.locationtech.spatial4j.shape.Shape;
/**
* A boolean ValueSource that compares a shape from a provided ValueSource with a given Shape and sees
* Compares a shape from a provided {@link ShapeValuesSource} with a given Shape and sees
* if it matches a given {@link SpatialOperation} (the predicate).
*
* Consumers should call {@link #iterator(LeafReaderContext, DocIdSetIterator)} to obtain a
* {@link TwoPhaseIterator} over a particular {@link DocIdSetIterator}. The initial DocIdSetIterator
* will be used as the approximation, and the {@link SpatialOperation} comparison will only be
* performed in {@link TwoPhaseIterator#matches()}
*
* @lucene.experimental
*/
public class ShapePredicateValueSource extends ValueSource {
private final ValueSource shapeValuesource;//the left hand side
public class ShapeValuesPredicate {
private final ShapeValuesSource shapeValuesource;//the left hand side
private final SpatialOperation op;
private final Shape queryShape;//the right hand side (constant)
@ -50,41 +49,28 @@ public class ShapePredicateValueSource extends ValueSource {
* @param op the predicate
* @param queryShape The shape on the right-hand (query) side.
*/
public ShapePredicateValueSource(ValueSource shapeValuesource, SpatialOperation op, Shape queryShape) {
public ShapeValuesPredicate(ShapeValuesSource shapeValuesource, SpatialOperation op, Shape queryShape) {
this.shapeValuesource = shapeValuesource;
this.op = op;
this.queryShape = queryShape;
}
@Override
public String description() {
public String toString() {
return shapeValuesource + " " + op + " " + queryShape;
}
@Override
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
shapeValuesource.createWeight(context, searcher);
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
final FunctionValues shapeValues = shapeValuesource.getValues(context, readerContext);
return new BoolDocValues(this) {
public TwoPhaseIterator iterator(LeafReaderContext ctx, DocIdSetIterator approximation) throws IOException {
final ShapeValues shapeValues = shapeValuesource.getValues(ctx);
return new TwoPhaseIterator(approximation) {
@Override
public boolean boolVal(int doc) throws IOException {
Shape indexedShape = (Shape) shapeValues.objectVal(doc);
if (indexedShape == null)
return false;
return op.evaluate(indexedShape, queryShape);
public boolean matches() throws IOException {
return shapeValues.advanceExact(approximation.docID()) && op.evaluate(shapeValues.value(), queryShape);
}
@Override
public Explanation explain(int doc) throws IOException {
Explanation exp = super.explain(doc);
List<Explanation> details = new ArrayList<>(Arrays.asList(exp.getDetails()));
details.add(shapeValues.explain(doc));
return Explanation.match(exp.getValue(), exp.getDescription(), details);
public float matchCost() {
return 100; // is this necessary?
}
};
}
@ -94,7 +80,7 @@ public class ShapePredicateValueSource extends ValueSource {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShapePredicateValueSource that = (ShapePredicateValueSource) o;
ShapeValuesPredicate that = (ShapeValuesPredicate) o;
if (!shapeValuesource.equals(that.shapeValuesource)) return false;
if (!op.equals(that.op)) return false;

View File

@ -16,29 +16,28 @@
*/
package org.apache.lucene.spatial.vector;
import org.apache.lucene.index.NumericDocValues;
import org.locationtech.spatial4j.distance.DistanceCalculator;
import org.locationtech.spatial4j.shape.Point;
import java.io.IOException;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.locationtech.spatial4j.distance.DistanceCalculator;
import org.locationtech.spatial4j.shape.Point;
/**
* An implementation of the Lucene ValueSource model that returns the distance
* for a {@link PointVectorStrategy}.
* A DoubleValuesSource that returns the distance for a {@link PointVectorStrategy}.
*
* @lucene.internal
*/
public class DistanceValueSource extends ValueSource {
public class DistanceValueSource extends DoubleValuesSource {
private PointVectorStrategy strategy;
private final Point from;
private final double multiplier;
private final double nullValue;
/**
* Constructor.
@ -47,13 +46,14 @@ public class DistanceValueSource extends ValueSource {
this.strategy = strategy;
this.from = from;
this.multiplier = multiplier;
this.nullValue = 180 * multiplier;
}
/**
* Returns the ValueSource description.
*/
@Override
public String description() {
public String toString() {
return "DistanceValueSource("+strategy+", "+from+")";
}
@ -61,55 +61,35 @@ public class DistanceValueSource extends ValueSource {
* Returns the FunctionValues used by the function query.
*/
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
LeafReader reader = readerContext.reader();
final NumericDocValues ptX = DocValues.getNumeric(reader, strategy.getFieldNameX());
final NumericDocValues ptY = DocValues.getNumeric(reader, strategy.getFieldNameY());
return new FunctionValues() {
private int lastDocID = -1;
return DoubleValues.withDefault(new DoubleValues() {
private final Point from = DistanceValueSource.this.from;
private final DistanceCalculator calculator = strategy.getSpatialContext().getDistCalc();
private final double nullValue =
(strategy.getSpatialContext().isGeo() ? 180 * multiplier : Double.MAX_VALUE);
private double getDocValue(NumericDocValues values, int doc) throws IOException {
int curDocID = values.docID();
if (doc > curDocID) {
curDocID = values.advance(doc);
}
if (doc == curDocID) {
return Double.longBitsToDouble(values.longValue());
} else {
return 0.0;
}
@Override
public double doubleValue() throws IOException {
double x = Double.longBitsToDouble(ptX.longValue());
double y = Double.longBitsToDouble(ptY.longValue());
return calculator.distance(from, x, y) * multiplier;
}
@Override
public float floatVal(int doc) throws IOException {
return (float) doubleVal(doc);
public boolean advanceExact(int doc) throws IOException {
return ptX.advanceExact(doc) && ptY.advanceExact(doc);
}
@Override
public double doubleVal(int doc) throws IOException {
// make sure it has minX and area
double x = getDocValue(ptX, doc);
if (ptX.docID() == doc) {
double y = getDocValue(ptY, doc);
assert ptY.docID() == doc;
return calculator.distance(from, x, y) * multiplier;
}
return nullValue;
}
}, nullValue);
}
@Override
public String toString(int doc) throws IOException {
return description() + "=" + floatVal(doc);
}
};
@Override
public boolean needsScores() {
return false;
}
@Override

View File

@ -16,18 +16,30 @@
*/
package org.apache.lucene.spatial.vector;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.document.DoubleDocValuesField;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.queries.function.FunctionRangeQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
@ -169,12 +181,12 @@ public class PointVectorStrategy extends SpatialStrategy {
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
return new DistanceValueSource(this, queryPoint, multiplier);
}
@Override
public ConstantScoreQuery makeQuery(SpatialArgs args) {
public Query makeQuery(SpatialArgs args) {
if(! SpatialOperation.is( args.getOperation(),
SpatialOperation.Intersects,
SpatialOperation.IsWithin ))
@ -186,13 +198,7 @@ public class PointVectorStrategy extends SpatialStrategy {
} else if (shape instanceof Circle) {
Circle circle = (Circle)shape;
Rectangle bbox = circle.getBoundingBox();
Query approxQuery = makeWithin(bbox);
BooleanQuery.Builder bqBuilder = new BooleanQuery.Builder();
FunctionRangeQuery vsRangeQuery =
new FunctionRangeQuery(makeDistanceValueSource(circle.getCenter()), 0.0, circle.getRadius(), true, true);
bqBuilder.add(approxQuery, BooleanClause.Occur.FILTER);//should have lowest "cost" value; will drive iteration
bqBuilder.add(vsRangeQuery, BooleanClause.Occur.FILTER);
return new ConstantScoreQuery(bqBuilder.build());
return new DistanceRangeQuery(makeWithin(bbox), makeDistanceValueSource(circle.getCenter()), circle.getRadius());
} else {
throw new UnsupportedOperationException("Only Rectangles and Circles are currently supported, " +
"found [" + shape.getClass() + "]");//TODO
@ -237,4 +243,71 @@ public class PointVectorStrategy extends SpatialStrategy {
//TODO try doc-value range query?
throw new UnsupportedOperationException("An index is required for this operation.");
}
private static class DistanceRangeQuery extends Query {
final Query inner;
final DoubleValuesSource distanceSource;
final double limit;
private DistanceRangeQuery(Query inner, DoubleValuesSource distanceSource, double limit) {
this.inner = inner;
this.distanceSource = distanceSource;
this.limit = limit;
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query rewritten = inner.rewrite(reader);
if (rewritten == inner)
return this;
return new DistanceRangeQuery(rewritten, distanceSource, limit);
}
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
Weight w = inner.createWeight(searcher, needsScores, 1f);
return new ConstantScoreWeight(this, boost) {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
Scorer in = w.scorer(context);
if (in == null)
return null;
DoubleValues v = distanceSource.getValues(context, DoubleValuesSource.fromScorer(in));
DocIdSetIterator approximation = in.iterator();
TwoPhaseIterator twoPhase = new TwoPhaseIterator(approximation) {
@Override
public boolean matches() throws IOException {
return v.advanceExact(approximation.docID()) && v.doubleValue() <= limit;
}
@Override
public float matchCost() {
return 100; // distance calculation can be heavy!
}
};
return new ConstantScoreScorer(this, score(), twoPhase);
}
};
}
@Override
public String toString(String field) {
return "DistanceRangeQuery(" + inner.toString(field) + "; " + distanceSource.toString() + " < " + limit + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DistanceRangeQuery that = (DistanceRangeQuery) o;
return Objects.equals(inner, that.inner) &&
Objects.equals(distanceSource, that.distanceSource) && limit == that.limit;
}
@Override
public int hashCode() {
return Objects.hash(inner, distanceSource, limit);
}
}
}

View File

@ -84,9 +84,6 @@ public class DistanceStrategyTest extends StrategyTestCase {
adoc("100", ctx.makePoint(2, 1));
adoc("101", ctx.makePoint(-1, 4));
adoc("103", (Shape)null);//test score for nothing
adoc("999", ctx.makePoint(2, 1));//test deleted
commit();
deleteDoc("999");
commit();
//FYI distances are in docid order
checkDistValueSource(ctx.makePoint(4, 3), 2.8274937f, 5.0898066f, 180f);
@ -100,9 +97,6 @@ public class DistanceStrategyTest extends StrategyTestCase {
Point p101 = ctx.makePoint(-1.001, 4.001);
adoc("101", p101);
adoc("103", (Shape)null);//test score for nothing
adoc("999", ctx.makePoint(2, 1));//test deleted
commit();
deleteDoc("999");
commit();
double dist = ctx.getDistCalc().distance(p100, p101);

View File

@ -18,10 +18,6 @@ package org.apache.lucene.spatial;
import java.io.IOException;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericDocValuesField;
@ -30,7 +26,7 @@ import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
@ -46,6 +42,10 @@ 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.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
/**
* This class serves as example code to show how to use the Lucene spatial
@ -167,7 +167,7 @@ public class SpatialExample extends LuceneTestCase {
//--Match all, order by distance ascending
{
Point pt = ctx.makePoint(60, -50);
ValueSource valueSource = strategy.makeDistanceValueSource(pt, DistanceUtils.DEG_TO_KM);//the distance (in km)
DoubleValuesSource valueSource = strategy.makeDistanceValueSource(pt, DistanceUtils.DEG_TO_KM);//the distance (in km)
Sort distSort = new Sort(valueSource.getSortField(false)).rewrite(indexSearcher);//false=asc dist
TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort);
assertDocMatchedIds(indexSearcher, docs, 4, 20, 2);

View File

@ -29,23 +29,21 @@ import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.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.index.LeafReaderContext;
import org.apache.lucene.index.Term;
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.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialArgsParser;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Shape;
public abstract class StrategyTestCase extends SpatialTestCase {
@ -212,25 +210,18 @@ public abstract class StrategyTestCase extends SpatialTestCase {
}
/** scores[] are in docId order */
protected void checkValueSource(ValueSource vs, float scores[], float delta) throws IOException {
FunctionQuery q = new FunctionQuery(vs);
protected void checkValueSource(DoubleValuesSource vs, float scores[], float delta) throws IOException {
// //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 is sorted but we actually don't care about the order
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);
for (LeafReaderContext ctx : indexSearcher.getTopReaderContext().leaves()) {
DoubleValues v = vs.getValues(ctx, null);
int count = ctx.reader().maxDoc();
for (int i = 0; i < count; i++) {
assertTrue(v.advanceExact(i));
int doc = i + ctx.docBase;
assertEquals("Not equal for doc " + doc, v.doubleValue(), (double) scores[doc], delta);
}
}
CheckHits.checkExplanations(q, "", indexSearcher);
}
protected void testOperation(Shape indexedShape, SpatialOperation operation,

View File

@ -29,11 +29,11 @@ import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoPathFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;

View File

@ -81,7 +81,7 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
return GeoBBoxFactory.makeGeoBBox(planetModel, maxLat, minLat, leftLon, rightLon);
}
abstract class Geo3dRectIntersectionTestHelper extends RectIntersectionTestHelper<Geo3dShape> {
public abstract class Geo3dRectIntersectionTestHelper extends RectIntersectionTestHelper<Geo3dShape> {
public Geo3dRectIntersectionTestHelper(SpatialContext ctx) {
super(ctx);

View File

@ -25,12 +25,13 @@ import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term;
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.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource;
import org.apache.lucene.spatial.query.SpatialArgs;
@ -257,23 +258,21 @@ public class BBoxStrategy extends SpatialStrategy {
//---------------------------------
/**
* Provides access to each rectangle per document as a ValueSource in which
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
* Shape}.
* Provides access to each rectangle per document
*/ //TODO raise to SpatialStrategy
public ValueSource makeShapeValueSource() {
public ShapeValuesSource makeShapeValueSource() {
return new BBoxValueSource(this);
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
//TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
}
/** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a
* convenience method. */
public ValueSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
public DoubleValuesSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
return new BBoxOverlapRatioValueSource(
makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0);
}

View File

@ -17,16 +17,14 @@
package org.apache.solr.legacy;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/**
* A ValueSource in which the indexed Rectangle is returned from
@ -34,7 +32,7 @@ import org.locationtech.spatial4j.shape.Rectangle;
*
* @lucene.internal
*/
class BBoxValueSource extends ValueSource {
class BBoxValueSource extends ShapeValuesSource {
private final BBoxStrategy strategy;
@ -43,76 +41,34 @@ class BBoxValueSource extends ValueSource {
}
@Override
public String description() {
public String toString() {
return "bboxShape(" + strategy.getFieldName() + ")";
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
LeafReader reader = readerContext.reader();
final NumericDocValues minX = DocValues.getNumeric(reader, strategy.field_minX);
final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY);
final NumericDocValues maxX = DocValues.getNumeric(reader, strategy.field_maxX);
final NumericDocValues maxY = DocValues.getNumeric(reader, strategy.field_maxY);
public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
final DoubleValues minX = DoubleValuesSource.fromDoubleField(strategy.field_minX).getValues(readerContext, null);
final DoubleValues minY = DoubleValuesSource.fromDoubleField(strategy.field_minY).getValues(readerContext, null);
final DoubleValues maxX = DoubleValuesSource.fromDoubleField(strategy.field_maxX).getValues(readerContext, null);
final DoubleValues maxY = DoubleValuesSource.fromDoubleField(strategy.field_maxY).getValues(readerContext, null);
//reused
final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
return new FunctionValues() {
private int lastDocID = -1;
return new ShapeValues() {
private double getDocValue(NumericDocValues values, int doc) throws IOException {
int curDocID = values.docID();
if (doc > curDocID) {
curDocID = values.advance(doc);
}
if (doc == curDocID) {
return Double.longBitsToDouble(values.longValue());
} else {
return 0.0;
}
@Override
public boolean advanceExact(int doc) throws IOException {
return minX.advanceExact(doc) && maxX.advanceExact(doc) && minY.advanceExact(doc) && maxY.advanceExact(doc);
}
@Override
public Object objectVal(int doc) throws IOException {
if (doc < lastDocID) {
throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + doc);
}
lastDocID = doc;
double minXValue = getDocValue(minX, doc);
if (minX.docID() != doc) {
return null;
} else {
double minYValue = getDocValue(minY, doc);
double maxXValue = getDocValue(maxX, doc);
double maxYValue = getDocValue(maxY, doc);
rect.reset(minXValue, maxXValue, minYValue, maxYValue);
return rect;
}
public Shape value() throws IOException {
rect.reset(minX.doubleValue(), maxX.doubleValue(), minY.doubleValue(), maxY.doubleValue());
return rect;
}
@Override
public String strVal(int doc) throws IOException {//TODO support WKT output once Spatial4j does
Object v = objectVal(doc);
return v == null ? null : v.toString();
}
@Override
public boolean exists(int doc) throws IOException {
getDocValue(minX, doc);
return minX.docID() == doc;
}
@Override
public Explanation explain(int doc) throws IOException {
return Explanation.match(Float.NaN, toString(doc));
}
@Override
public String toString(int doc) throws IOException {
return description() + '=' + strVal(doc);
}
};
}

View File

@ -16,17 +16,13 @@
*/
package org.apache.solr.legacy;
import org.apache.lucene.index.NumericDocValues;
import java.io.IOException;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.locationtech.spatial4j.distance.DistanceCalculator;
import org.locationtech.spatial4j.shape.Point;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import java.io.IOException;
import java.util.Map;
/**
* An implementation of the Lucene ValueSource model that returns the distance
@ -34,11 +30,13 @@ import java.util.Map;
*
* @lucene.internal
*/
public class DistanceValueSource extends ValueSource {
public class DistanceValueSource extends DoubleValuesSource {
private PointVectorStrategy strategy;
private final Point from;
private final double multiplier;
private final double nullValue;
/**
* Constructor.
@ -47,13 +45,15 @@ public class DistanceValueSource extends ValueSource {
this.strategy = strategy;
this.from = from;
this.multiplier = multiplier;
this.nullValue =
(strategy.getSpatialContext().isGeo() ? 180 * multiplier : Double.MAX_VALUE);
}
/**
* Returns the ValueSource description.
*/
@Override
public String description() {
public String toString() {
return "DistanceValueSource("+strategy+", "+from+")";
}
@ -61,55 +61,30 @@ public class DistanceValueSource extends ValueSource {
* Returns the FunctionValues used by the function query.
*/
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
LeafReader reader = readerContext.reader();
public DoubleValues getValues(LeafReaderContext readerContext, DoubleValues scores) throws IOException {
final NumericDocValues ptX = DocValues.getNumeric(reader, strategy.getFieldNameX());
final NumericDocValues ptY = DocValues.getNumeric(reader, strategy.getFieldNameY());
final DoubleValues ptX = DoubleValuesSource.fromDoubleField(strategy.getFieldNameX()).getValues(readerContext, null);
final DoubleValues ptY = DoubleValuesSource.fromDoubleField(strategy.getFieldNameY()).getValues(readerContext, null);
final DistanceCalculator calculator = strategy.getSpatialContext().getDistCalc();
return new FunctionValues() {
return DoubleValues.withDefault(new DoubleValues() {
private int lastDocID = -1;
private final Point from = DistanceValueSource.this.from;
private final DistanceCalculator calculator = strategy.getSpatialContext().getDistCalc();
private final double nullValue =
(strategy.getSpatialContext().isGeo() ? 180 * multiplier : Double.MAX_VALUE);
private double getDocValue(NumericDocValues values, int doc) throws IOException {
int curDocID = values.docID();
if (doc > curDocID) {
curDocID = values.advance(doc);
}
if (doc == curDocID) {
return Double.longBitsToDouble(values.longValue());
} else {
return 0.0;
}
@Override
public double doubleValue() throws IOException {
return calculator.distance(from, ptX.doubleValue(), ptY.doubleValue()) * multiplier;
}
@Override
public float floatVal(int doc) throws IOException {
return (float) doubleVal(doc);
public boolean advanceExact(int doc) throws IOException {
return ptX.advanceExact(doc) && ptY.advanceExact(doc);
}
}, nullValue);
@Override
public double doubleVal(int doc) throws IOException {
// make sure it has minX and area
double x = getDocValue(ptX, doc);
if (ptX.docID() == doc) {
double y = getDocValue(ptY, doc);
assert ptY.docID() == doc;
return calculator.distance(from, x, y) * multiplier;
}
return nullValue;
}
}
@Override
public String toString(int doc) throws IOException {
return description() + "=" + floatVal(doc);
}
};
@Override
public boolean needsScores() {
return false;
}
@Override

View File

@ -23,15 +23,11 @@ import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.solr.legacy.LegacyDoubleField;
import org.apache.solr.legacy.LegacyFieldType;
import org.apache.solr.legacy.LegacyNumericRangeQuery;
import org.apache.solr.legacy.LegacyNumericType;
import org.apache.lucene.queries.function.FunctionRangeQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.FunctionMatchQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
@ -218,7 +214,7 @@ public class PointVectorStrategy extends SpatialStrategy {
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
return new DistanceValueSource(this, queryPoint, multiplier);
}
@ -237,10 +233,11 @@ public class PointVectorStrategy extends SpatialStrategy {
Rectangle bbox = circle.getBoundingBox();
Query approxQuery = makeWithin(bbox);
BooleanQuery.Builder bqBuilder = new BooleanQuery.Builder();
FunctionRangeQuery vsRangeQuery =
new FunctionRangeQuery(makeDistanceValueSource(circle.getCenter()), 0.0, circle.getRadius(), true, true);
double r = circle.getRadius();
FunctionMatchQuery vsMatchQuery = new FunctionMatchQuery(makeDistanceValueSource(circle.getCenter()),
v -> 0 <= v && v <= r);
bqBuilder.add(approxQuery, BooleanClause.Occur.FILTER);//should have lowest "cost" value; will drive iteration
bqBuilder.add(vsRangeQuery, BooleanClause.Occur.FILTER);
bqBuilder.add(vsMatchQuery, BooleanClause.Occur.FILTER);
return new ConstantScoreQuery(bqBuilder.build());
} else {
throw new UnsupportedOperationException("Only Rectangles and Circles are currently supported, " +

View File

@ -20,9 +20,11 @@ import java.io.IOException;
import java.util.Iterator;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
@ -35,8 +37,6 @@ import org.apache.solr.response.JSONResponseWriter;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.schema.AbstractSpatialFieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SyntaxError;
import org.locationtech.spatial4j.io.GeoJSONWriter;
import org.locationtech.spatial4j.io.ShapeWriter;
import org.locationtech.spatial4j.io.SupportedFormats;
@ -87,7 +87,7 @@ public class GeoTransformerFactory extends TransformerFactory
updater.display = display;
updater.display_error = display+"_error";
ValueSource shapes = null;
final ShapeValuesSource shapes;
AbstractSpatialFieldType<?> sdv = (AbstractSpatialFieldType<?>)sf.getType();
SpatialStrategy strategy = sdv.getStrategy(fname);
if(strategy instanceof CompositeSpatialStrategy) {
@ -98,6 +98,8 @@ public class GeoTransformerFactory extends TransformerFactory
shapes = ((SerializedDVStrategy)strategy)
.makeShapeValueSource();
}
else
shapes = null;
String writerName = params.get("w", "GeoJSON");
@ -122,20 +124,24 @@ public class GeoTransformerFactory extends TransformerFactory
// Using ValueSource
if(shapes!=null) {
// we don't really need the qparser... just so we can reuse valueSource
QParser parser = new QParser(null,null,params, req) {
return new DocTransformer() {
@Override
public Query parse() throws SyntaxError {
return new MatchAllDocsQuery();
public String getName() {
return display;
}
};
return new ValueSourceAugmenter(display, parser, shapes) {
@Override
protected void setValue(SolrDocument doc, Object val) {
updater.setValue(doc, val);
public void transform(SolrDocument doc, int docid, float score) throws IOException {
int leafOrd = ReaderUtil.subIndex(docid, context.getSearcher().getTopReaderContext().leaves());
LeafReaderContext ctx = context.getSearcher().getTopReaderContext().leaves().get(leafOrd);
ShapeValues values = shapes.getValues(ctx);
int segmentDoc = docid - ctx.docBase;
if (values.advanceExact(segmentDoc)) {
updater.setValue(doc, values.value());
}
}
};
}
// Using the raw stored values
@ -160,6 +166,7 @@ public class GeoTransformerFactory extends TransformerFactory
}
};
}
}
class GeoFieldUpdater {

View File

@ -34,10 +34,12 @@ import com.google.common.cache.CacheBuilder;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queries.function.FunctionQuery;
import org.apache.lucene.queries.function.FunctionScoreQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial.SpatialStrategy;
@ -356,12 +358,12 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
String scoreParam = (localParams == null ? null : localParams.get(SCORE_PARAM));
//We get the valueSource for the score then the filter and combine them.
ValueSource valueSource = getValueSourceFromSpatialArgs(parser, field, spatialArgs, scoreParam, strategy);
DoubleValuesSource valueSource = getValueSourceFromSpatialArgs(parser, field, spatialArgs, scoreParam, strategy);
if (valueSource == null) {
return strategy.makeQuery(spatialArgs); //assumed constant scoring
}
FunctionQuery functionQuery = new FunctionQuery(valueSource);
FunctionScoreQuery functionQuery = new FunctionScoreQuery(new MatchAllDocsQuery(), valueSource);
if (localParams != null && !localParams.getBool(FILTER_PARAM, true))
return functionQuery;
@ -383,7 +385,7 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
return supportedScoreModes;
}
protected ValueSource getValueSourceFromSpatialArgs(QParser parser, SchemaField field, SpatialArgs spatialArgs, String score, T strategy) {
protected DoubleValuesSource getValueSourceFromSpatialArgs(QParser parser, SchemaField field, SpatialArgs spatialArgs, String score, T strategy) {
if (score == null) {
return null;
}

View File

@ -23,13 +23,13 @@ import java.util.List;
import java.util.Map;
import org.apache.lucene.index.DocValuesType;
import org.apache.solr.legacy.LegacyFieldType;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource;
import org.apache.solr.legacy.BBoxStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
import org.apache.solr.common.SolrException;
import org.apache.solr.legacy.BBoxStrategy;
import org.apache.solr.legacy.LegacyFieldType;
import org.apache.solr.search.QParser;
import org.locationtech.spatial4j.shape.Rectangle;
@ -156,7 +156,7 @@ public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements
}
@Override
protected ValueSource getValueSourceFromSpatialArgs(QParser parser, SchemaField field, SpatialArgs spatialArgs, String scoreParam, BBoxStrategy strategy) {
protected DoubleValuesSource getValueSourceFromSpatialArgs(QParser parser, SchemaField field, SpatialArgs spatialArgs, String scoreParam, BBoxStrategy strategy) {
if (scoreParam == null) {
return null;
}

View File

@ -18,16 +18,15 @@
package org.apache.solr.schema;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.LeafFieldComparator;
@ -177,7 +176,7 @@ public class LatLonPointSpatialField extends AbstractSpatialFieldType implements
}
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
if (!docValues) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
getFieldName() + " must have docValues enabled to support this feature");
@ -191,7 +190,7 @@ public class LatLonPointSpatialField extends AbstractSpatialFieldType implements
/**
* A {@link ValueSource} based around {@code LatLonDocValuesField#newDistanceSort(String, double, double)}.
*/
private static class DistanceSortValueSource extends ValueSource {
private static class DistanceSortValueSource extends DoubleValuesSource {
private final String fieldName;
private final Point queryPoint;
private final double multiplier;
@ -218,41 +217,38 @@ public class LatLonPointSpatialField extends AbstractSpatialFieldType implements
}
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
return new DoubleDocValues(this) {
public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
return new DoubleValues() {
@SuppressWarnings("unchecked")
final FieldComparator<Double> comparator =
(FieldComparator<Double>) getSortField(false).getComparator(1, 1);
final LeafFieldComparator leafComparator = comparator.getLeafComparator(readerContext);
final LeafFieldComparator leafComparator = comparator.getLeafComparator(ctx);
final double mult = multiplier; // so it's a local field
// Since this computation is expensive, it's worth caching it just in case.
double cacheDoc = -1;
double cacheVal = Double.POSITIVE_INFINITY;
double value = Double.POSITIVE_INFINITY;
@Override
public double doubleVal(int doc) {
if (cacheDoc != doc) {
try {
leafComparator.copy(0, doc);
cacheVal = comparator.value(0) * mult;
cacheDoc = doc;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return cacheVal;
public double doubleValue() throws IOException {
return value;
}
@Override
public boolean exists(int doc) {
return !Double.isInfinite(doubleVal(doc));
public boolean advanceExact(int doc) throws IOException {
leafComparator.copy(0, doc);
value = comparator.value(0) * mult;
return true;
}
};
}
@Override
public String description() {
public boolean needsScores() {
return false;
}
@Override
public String toString() {
return "distSort(" + fieldName + ", " + queryPoint + ", mult:" + multiplier + ")";
}

View File

@ -24,9 +24,8 @@ import java.util.Map;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.query.SpatialArgsParser;
@ -103,24 +102,24 @@ public class RptWithGeometrySpatialField extends AbstractSpatialFieldType<Compos
}
@Override
public ValueSource makeShapeValueSource() {
public ShapeValuesSource makeShapeValueSource() {
return new CachingShapeValuesource(super.makeShapeValueSource(), getFieldName());
}
}
private static class CachingShapeValuesource extends ValueSource {
private static class CachingShapeValuesource extends ShapeValuesSource {
private final ValueSource targetValueSource;
private final ShapeValuesSource targetValueSource;
private final String fieldName;
private CachingShapeValuesource(ValueSource targetValueSource, String fieldName) {
private CachingShapeValuesource(ShapeValuesSource targetValueSource, String fieldName) {
this.targetValueSource = targetValueSource;
this.fieldName = fieldName;
}
@Override
public String description() {
return "cache(" + targetValueSource.description() + ")";
public String toString() {
return "cache(" + targetValueSource.toString() + ")";
}
@Override
@ -142,10 +141,9 @@ public class RptWithGeometrySpatialField extends AbstractSpatialFieldType<Compos
return result;
}
@SuppressWarnings("unchecked")
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
final FunctionValues targetFuncValues = targetValueSource.getValues(context, readerContext);
public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
final ShapeValues targetFuncValues = targetValueSource.getValues(readerContext);
// The key is a pair of leaf reader with a docId relative to that reader. The value is a Map from field to Shape.
final SolrCache<PerSegCacheKey,Shape> cache =
SolrRequestInfo.getRequestInfo().getReq().getSearcher().getCache(CACHE_KEY_PREFIX + fieldName);
@ -153,24 +151,20 @@ public class RptWithGeometrySpatialField extends AbstractSpatialFieldType<Compos
return targetFuncValues; // no caching; no configured cache
}
return new FunctionValues() {
return new ShapeValues() {
int docId = -1;
Shape shape = null;
private void setShapeFromDoc(int doc) throws IOException {
if (docId == doc) {
return;
}
docId = doc;
@Override
public Shape value() throws IOException {
//lookup in cache
IndexReader.CacheHelper cacheHelper = readerContext.reader().getCoreCacheHelper();
if (cacheHelper == null) {
throw new IllegalStateException("Leaf " + readerContext.reader() + " is not suited for caching");
}
PerSegCacheKey key = new PerSegCacheKey(cacheHelper.getKey(), doc);
shape = cache.get(key);
PerSegCacheKey key = new PerSegCacheKey(cacheHelper.getKey(), docId);
Shape shape = cache.get(key);
if (shape == null) {
shape = (Shape) targetFuncValues.objectVal(doc);
shape = targetFuncValues.value();
if (shape != null) {
cache.put(key, shape);
}
@ -180,31 +174,15 @@ public class RptWithGeometrySpatialField extends AbstractSpatialFieldType<Compos
((JtsGeometry) shape).index(); // TODO would be nice if some day we didn't have to cast
}
}
}
// Use the cache for exists & objectVal;
@Override
public boolean exists(int doc) throws IOException {
setShapeFromDoc(doc);
return shape != null;
}
@Override
public Object objectVal(int doc) throws IOException {
setShapeFromDoc(doc);
return shape;
}
@Override
public Explanation explain(int doc) throws IOException {
return targetFuncValues.explain(doc);
public boolean advanceExact(int doc) throws IOException {
this.docId = doc;
return targetFuncValues.advanceExact(doc);
}
@Override
public String toString(int doc) throws IOException {
return targetFuncValues.toString(doc);
}
};
}

View File

@ -20,9 +20,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.ConstNumberSource;
import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource;
@ -38,6 +35,9 @@ import org.apache.solr.search.SyntaxError;
import org.apache.solr.search.ValueSourceParser;
import org.apache.solr.util.DistanceUnits;
import org.apache.solr.util.SpatialUtils;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
/**
* Parses "geodist" creating {@link HaversineConstFunction} or {@link HaversineFunction}
@ -135,7 +135,7 @@ public class GeoDistValueSourceParser extends ValueSourceParser {
SpatialStrategy strategy = ((SpatialStrategyMultiValueSource) mv2).strategy;
DistanceUnits distanceUnits = ((SpatialStrategyMultiValueSource) mv2).distanceUnits;
Point queryPoint = strategy.getSpatialContext().makePoint(constants[1], constants[0]);
return strategy.makeDistanceValueSource(queryPoint, distanceUnits.multiplierFromDegreesToThisUnit());
return ValueSource.fromDoubleValuesSource(strategy.makeDistanceValueSource(queryPoint, distanceUnits.multiplierFromDegreesToThisUnit()));
}
if (constants != null && other instanceof VectorValueSource) {

View File

@ -20,14 +20,10 @@ import java.text.ParseException;
import java.util.Arrays;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.apache.solr.legacy.BBoxStrategy;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.core.SolrCore;
import org.apache.solr.legacy.BBoxStrategy;
import org.apache.solr.schema.BBoxField;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
@ -35,6 +31,10 @@ import org.apache.solr.util.SpatialUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
/**
* Test Solr 4's new spatial capabilities from the new Lucene spatial module. Don't thoroughly test it here because