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="library" scope="TEST" name="JUnit" level="project" />
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" /> <orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
<orderEntry type="module" module-name="lucene-core" /> <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="spatial3d" />
<orderEntry type="module" module-name="backward-codecs" />
<orderEntry type="module" module-name="analysis-common" scope="TEST"/> <orderEntry type="module" module-name="analysis-common" scope="TEST"/>
</component> </component>
</module> </module>

View File

@ -92,6 +92,11 @@ API Changes
* LUCENE-7723: DoubleValuesSource enforces implementation of equals() and * LUCENE-7723: DoubleValuesSource enforces implementation of equals() and
hashCode() (Alan Woodward) 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 Bug Fixes
* LUCENE-7626: IndexWriter will no longer accept broken token offsets * 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.List;
import java.util.Properties; import java.util.Properties;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.benchmark.byTask.utils.Config; import org.apache.lucene.benchmark.byTask.utils.Config;
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.DoubleValuesSource;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation; 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}. * 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); Query filterQuery = strategy.makeQuery(args);
if (score) { if (score) {
//wrap with distance computing query //wrap with distance computing query
ValueSource valueSource = strategy.makeDistanceValueSource(shape.getCenter()); DoubleValuesSource valueSource = strategy.makeDistanceValueSource(shape.getCenter());
return new BooleanQuery.Builder() return new FunctionScoreQuery(filterQuery, valueSource);
.add(new FunctionQuery(valueSource), BooleanClause.Occur.MUST)//matches everything and provides score
.add(filterQuery, BooleanClause.Occur.FILTER)//filters (score isn't used)
.build();
} else { } else {
return filterQuery; // assume constant scoring return filterQuery; // assume constant scoring
} }

View File

@ -35,4 +35,25 @@ public abstract class DoubleValues {
*/ */
public abstract boolean advanceExact(int doc) throws IOException; 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 * @return an Explanation for the value
* @throws IOException if an {@link IOException} occurs * @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 * 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 // Sorting by function
// //

View File

@ -31,7 +31,6 @@
<path id="classpath"> <path id="classpath">
<path refid="base.classpath"/> <path refid="base.classpath"/>
<path refid="spatialjar"/> <path refid="spatialjar"/>
<pathelement path="${queries.jar}" />
<pathelement path="${spatial3d.jar}" /> <pathelement path="${spatial3d.jar}" />
</path> </path>
@ -41,15 +40,12 @@
<pathelement path="src/test-files" /> <pathelement path="src/test-files" />
</path> </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}"> unless="javadocs-uptodate-${name}">
<invoke-module-javadoc> <invoke-module-javadoc>
<links> <links>
<link href="../backward-codecs"/>
<link href="../queries"/>
<link href="../misc"/>
<link href="../spatial3d"/> <link href="../spatial3d"/>
</links> </links>
</invoke-module-javadoc> </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; 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.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape; 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 * 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 * See {@link #makeDistanceValueSource(org.locationtech.spatial4j.shape.Point, double)} called with
* a multiplier of 1.0 (i.e. units of degrees). * 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); 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 * then the closest one is chosen. The result is multiplied by {@code multiplier}, which
* conveniently is used to get the desired units. * 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} * 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 * scores will be 1 for indexed points at the center of the query shape and as
* low as ~0.1 at its furthest edges. * low as ~0.1 at its furthest edges.
*/ */
public final ValueSource makeRecipDistanceValueSource(Shape queryShape) { public final DoubleValuesSource makeRecipDistanceValueSource(Shape queryShape) {
Rectangle bbox = queryShape.getBoundingBox(); Rectangle bbox = queryShape.getBoundingBox();
double diagonalDist = ctx.getDistCalc().distance( double diagonalDist = ctx.getDistCalc().distance(
ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY()); ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY());
double distToEdge = diagonalDist * 0.5; double distToEdge = diagonalDist * 0.5;
float c = (float)distToEdge * 0.1f;//one tenth 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 @Override

View File

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

View File

@ -17,78 +17,58 @@
package org.apache.lucene.spatial.bbox; package org.apache.lucene.spatial.bbox;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues; import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.search.Explanation; 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; import org.locationtech.spatial4j.shape.Rectangle;
/** /**
* A base class for calculating a spatial relevance rank per document from a provided * 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 * {@link ShapeValuesSource} returning a {@link
* org.locationtech.spatial4j.shape.Rectangle}. * org.locationtech.spatial4j.shape.Rectangle} per-document.
* <p> * <p>
* Implementers: remember to implement equals and hashCode if you have * Implementers: remember to implement equals and hashCode if you have
* fields! * fields!
* *
* @lucene.experimental * @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; this.bboxValueSource = bboxValueSource;
} }
@Override @Override
public void createWeight(Map context, IndexSearcher searcher) throws IOException { public String toString() {
bboxValueSource.createWeight(context, searcher); return getClass().getSimpleName()+"(" + bboxValueSource.toString() + "," + similarityDescription() + ")";
} }
@Override /** A comma-separated list of configurable items of the subclass to put into {@link #toString()}. */
public String description() {
return getClass().getSimpleName()+"(" + bboxValueSource.description() + "," + similarityDescription() + ")";
}
/** A comma-separated list of configurable items of the subclass to put into {@link #description()}. */
protected abstract String similarityDescription(); protected abstract String similarityDescription();
@Override @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); final ShapeValues shapeValues = bboxValueSource.getValues(readerContext);
return DoubleValues.withDefault(new DoubleValues() {
return new DoubleDocValues(this) {
@Override @Override
public double doubleVal(int doc) throws IOException { public double doubleValue() throws IOException {
//? limit to Rect or call getBoundingBox()? latter would encourage bad practice return score((Rectangle) shapeValues.value(), null);
final Rectangle rect = (Rectangle) shapeValues.objectVal(doc);
return rect==null ? 0 : score(rect, null);
} }
@Override @Override
public boolean exists(int doc) throws IOException { public boolean advanceExact(int doc) throws IOException {
return shapeValues.exists(doc); 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() { public int hashCode() {
return bboxValueSource.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.DocValuesType;
import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermQuery;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation; 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 * Provides access to each rectangle per document as a {@link ShapeValuesSource}
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
* Shape}.
*/ //TODO raise to SpatialStrategy */ //TODO raise to SpatialStrategy
public ValueSource makeShapeValueSource() { public ShapeValuesSource makeShapeValueSource() {
return new BBoxValueSource(this); return new BBoxValueSource(this);
} }
@Override @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. //TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx); return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
} }
/** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a /** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a
* convenience method. */ * convenience method. */
public ValueSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) { public DoubleValuesSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
return new BBoxOverlapRatioValueSource( return new BBoxOverlapRatioValueSource(
makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0); makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0);
} }

View File

@ -17,24 +17,22 @@
package org.apache.lucene.spatial.bbox; package org.apache.lucene.spatial.bbox;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.DocValues; import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.queries.function.FunctionValues; import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.search.Explanation;
import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/** /**
* A ValueSource in which the indexed Rectangle is returned from * A ShapeValuesSource returning a Rectangle from each document derived from four numeric fields
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
* *
* @lucene.internal * @lucene.internal
*/ */
class BBoxValueSource extends ValueSource { class BBoxValueSource extends ShapeValuesSource {
private final BBoxStrategy strategy; private final BBoxStrategy strategy;
@ -43,12 +41,12 @@ class BBoxValueSource extends ValueSource {
} }
@Override @Override
public String description() { public String toString() {
return "bboxShape(" + strategy.getFieldName() + ")"; return "bboxShape(" + strategy.getFieldName() + ")";
} }
@Override @Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
LeafReader reader = readerContext.reader(); LeafReader reader = readerContext.reader();
final NumericDocValues minX = DocValues.getNumeric(reader, strategy.field_minX); final NumericDocValues minX = DocValues.getNumeric(reader, strategy.field_minX);
final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY); final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY);
@ -58,61 +56,23 @@ class BBoxValueSource extends ValueSource {
//reused //reused
final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0); final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
return new FunctionValues() { return new ShapeValues() {
private int lastDocID = -1;
private double getDocValue(NumericDocValues values, int doc) throws IOException { @Override
int curDocID = values.docID(); public boolean advanceExact(int doc) throws IOException {
if (doc > curDocID) { return minX.advanceExact(doc) && minY.advanceExact(doc) && maxX.advanceExact(doc) && maxY.advanceExact(doc);
curDocID = values.advance(doc);
}
if (doc == curDocID) {
return Double.longBitsToDouble(values.longValue());
} else {
return 0.0;
}
} }
@Override @Override
public Object objectVal(int doc) throws IOException { public Shape value() throws IOException {
if (doc < lastDocID) { double minXValue = Double.longBitsToDouble(minX.longValue());
throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + doc); double minYValue = Double.longBitsToDouble(minY.longValue());
} double maxXValue = Double.longBitsToDouble(maxX.longValue());
lastDocID = doc; double maxYValue = Double.longBitsToDouble(maxY.longValue());
rect.reset(minXValue, maxXValue, minYValue, maxYValue);
double minXValue = getDocValue(minX, doc); return rect;
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;
}
} }
@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.Collections;
import java.util.List; 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.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.search.Query;
import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; 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.SpatialOperation;
import org.apache.lucene.spatial.query.UnsupportedSpatialOperation; import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy; 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 * A composite {@link SpatialStrategy} based on {@link RecursivePrefixTreeStrategy} (RPT) and
@ -86,7 +86,7 @@ public class CompositeSpatialStrategy extends SpatialStrategy {
} }
@Override @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 //TODO consider indexing center-point in DV? Guarantee contained by the shape, which could then be used for
// other purposes like faster WITHIN predicate? // other purposes like faster WITHIN predicate?
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -108,8 +108,8 @@ public class CompositeSpatialStrategy extends SpatialStrategy {
throw new UnsupportedSpatialOperation(pred); throw new UnsupportedSpatialOperation(pred);
} }
final ShapePredicateValueSource predicateValueSource = final ShapeValuesPredicate predicateValueSource =
new ShapePredicateValueSource(geometryStrategy.makeShapeValueSource(), pred, args.getShape()); new ShapeValuesPredicate(geometryStrategy.makeShapeValueSource(), pred, args.getShape());
//System.out.println("PredOpt: " + optimizePredicates); //System.out.println("PredOpt: " + optimizePredicates);
if (pred == SpatialOperation.Intersects && optimizePredicates) { if (pred == SpatialOperation.Intersects && optimizePredicates) {
// We have a smart Intersects impl // We have a smart Intersects impl

View File

@ -17,12 +17,9 @@
package org.apache.lucene.spatial.composite; package org.apache.lucene.spatial.composite;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext; 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.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.IndexSearcher; 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.Scorer;
import org.apache.lucene.search.TwoPhaseIterator; import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight; 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 * 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 * ShapeValuesSource is called to verify each hit from {@link TwoPhaseIterator#matches()}.
* from {@link TwoPhaseIterator#matches()}.
* *
* @lucene.experimental * @lucene.experimental
*/ */
public class CompositeVerifyQuery extends Query { 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.indexQuery = indexQuery;
this.predicateValueSource = predicateValueSource; this.predicateValueSource = predicateValueSource;
} }
@ -84,7 +82,6 @@ public class CompositeVerifyQuery extends Query {
@Override @Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException { public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
final Weight indexQueryWeight = indexQuery.createWeight(searcher, false, boost);//scores aren't unsupported final Weight indexQueryWeight = indexQuery.createWeight(searcher, false, boost);//scores aren't unsupported
final Map valueSourceContext = ValueSource.newContext(searcher);
return new ConstantScoreWeight(this, boost) { return new ConstantScoreWeight(this, boost) {
@ -96,21 +93,8 @@ public class CompositeVerifyQuery extends Query {
return null; return null;
} }
final FunctionValues predFuncValues = predicateValueSource.getValues(valueSourceContext, context); final TwoPhaseIterator predFuncValues = predicateValueSource.iterator(context, indexQueryScorer.iterator());
return new ConstantScoreScorer(this, score(), predFuncValues);
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);
} }
}; };
} }

View File

@ -17,11 +17,8 @@
package org.apache.lucene.spatial.composite; package org.apache.lucene.spatial.composite;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext; 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.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSet; 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.AbstractVisitingPrefixTreeQuery;
import org.apache.lucene.spatial.prefix.tree.Cell; import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.util.ShapeValuesPredicate;
import org.apache.lucene.util.DocIdSetBuilder; import org.apache.lucene.util.DocIdSetBuilder;
import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation; 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 * 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 * 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 * @lucene.internal
*/ */
public class IntersectsRPTVerifyQuery extends Query { public class IntersectsRPTVerifyQuery extends Query {
private final IntersectsDifferentiatingQuery intersectsDiffQuery; 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, public IntersectsRPTVerifyQuery(Shape queryShape, String fieldName, SpatialPrefixTree grid, int detailLevel,
int prefixGridScanLevel, ValueSource predicateValueSource) { int prefixGridScanLevel, ShapeValuesPredicate predicateValueSource) {
this.predicateValueSource = predicateValueSource; this.predicateValueSource = predicateValueSource;
this.intersectsDiffQuery = new IntersectsDifferentiatingQuery(queryShape, fieldName, grid, detailLevel, this.intersectsDiffQuery = new IntersectsDifferentiatingQuery(queryShape, fieldName, grid, detailLevel,
prefixGridScanLevel); prefixGridScanLevel);
@ -83,7 +81,6 @@ public class IntersectsRPTVerifyQuery extends Query {
@Override @Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException { public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
final Map valueSourceContext = ValueSource.newContext(searcher);
return new ConstantScoreWeight(this, boost) { return new ConstantScoreWeight(this, boost) {
@Override @Override
@ -110,9 +107,10 @@ public class IntersectsRPTVerifyQuery extends Query {
exactIterator = null; exactIterator = null;
} }
final FunctionValues predFuncValues = predicateValueSource.getValues(valueSourceContext, context);
final TwoPhaseIterator twoPhaseIterator = new TwoPhaseIterator(approxDISI) { final TwoPhaseIterator twoPhaseIterator = new TwoPhaseIterator(approxDISI) {
final TwoPhaseIterator predFuncValues = predicateValueSource.iterator(context, approxDISI);
@Override @Override
public boolean matches() throws IOException { public boolean matches() throws IOException {
final int doc = approxDISI.docID(); final int doc = approxDISI.docID();
@ -124,13 +122,12 @@ public class IntersectsRPTVerifyQuery extends Query {
return true; return true;
} }
} }
return predFuncValues.matches();
return predFuncValues.boolVal(doc);
} }
@Override @Override
public float matchCost() { 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 java.util.TreeMap;
import org.apache.lucene.index.IndexReaderContext; 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.Cell;
import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree; import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree;
import org.apache.lucene.util.Bits; import org.apache.lucene.util.Bits;
@ -76,7 +76,7 @@ public class NumberRangePrefixTreeStrategy extends RecursivePrefixTreeStrategy {
/** Unsupported. */ /** Unsupported. */
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) { public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
throw new UnsupportedOperationException(); 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.document.FieldType;
import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReaderContext; 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.SpatialStrategy;
import org.apache.lucene.spatial.prefix.tree.Cell; import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
@ -180,7 +180,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
} }
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) { public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() ); PointPrefixTreeFieldCacheProvider p = provider.get( getFieldName() );
if( p == null ) { if( p == null ) {
synchronized (this) {//double checked locking idiom is okay since provider is threadsafe 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.DataOutputStream;
import java.io.FilterOutputStream; import java.io.FilterOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import org.apache.lucene.document.BinaryDocValuesField; import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.LeafReaderContext; 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.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSetIterator; 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.IndexSearcher;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator; import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight; 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.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.DistanceToShapeValueSource; 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.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.io.BinaryCodec; import org.locationtech.spatial4j.io.BinaryCodec;
import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Point;
@ -100,7 +98,7 @@ public class SerializedDVStrategy extends SpatialStrategy {
} }
@Override @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. //TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx); return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
} }
@ -111,18 +109,15 @@ public class SerializedDVStrategy extends SpatialStrategy {
*/ */
@Override @Override
public Query makeQuery(SpatialArgs args) { public Query makeQuery(SpatialArgs args) {
ValueSource shapeValueSource = makeShapeValueSource(); ShapeValuesSource shapeValueSource = makeShapeValueSource();
ShapePredicateValueSource predicateValueSource = new ShapePredicateValueSource( ShapeValuesPredicate predicateValueSource = new ShapeValuesPredicate(shapeValueSource, args.getOperation(), args.getShape());
shapeValueSource, args.getOperation(), args.getShape());
return new PredicateValueSourceQuery(predicateValueSource); return new PredicateValueSourceQuery(predicateValueSource);
} }
/** /**
* Provides access to each shape per document as a ValueSource in which * Provides access to each shape per document
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
* Shape}.
*/ //TODO raise to SpatialStrategy */ //TODO raise to SpatialStrategy
public ValueSource makeShapeValueSource() { public ShapeValuesSource makeShapeValueSource() {
return new ShapeDocValueSource(getFieldName(), ctx.getBinaryCodec()); return new ShapeDocValueSource(getFieldName(), ctx.getBinaryCodec());
} }
@ -130,9 +125,9 @@ public class SerializedDVStrategy extends SpatialStrategy {
* by {@link TwoPhaseIterator}. * by {@link TwoPhaseIterator}.
*/ */
static class PredicateValueSourceQuery extends Query { 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; this.predicateValueSource = predicateValueSource;
} }
@ -142,21 +137,8 @@ public class SerializedDVStrategy extends SpatialStrategy {
@Override @Override
public Scorer scorer(LeafReaderContext context) throws IOException { public Scorer scorer(LeafReaderContext context) throws IOException {
DocIdSetIterator approximation = DocIdSetIterator.all(context.reader().maxDoc()); DocIdSetIterator approximation = DocIdSetIterator.all(context.reader().maxDoc());
final FunctionValues predFuncValues = predicateValueSource.getValues(null, context); TwoPhaseIterator it = predicateValueSource.iterator(context, approximation);
return new ConstantScoreScorer(this, score(), new TwoPhaseIterator(approximation) { return new ConstantScoreScorer(this, score(), it);
@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;
}
});
} }
}; };
} }
@ -184,7 +166,7 @@ public class SerializedDVStrategy extends SpatialStrategy {
* Implements a ValueSource by deserializing a Shape in from BinaryDocValues using BinaryCodec. * Implements a ValueSource by deserializing a Shape in from BinaryDocValues using BinaryCodec.
* @see #makeShapeValueSource() * @see #makeShapeValueSource()
*/ */
static class ShapeDocValueSource extends ValueSource { static class ShapeDocValueSource extends ShapeValuesSource {
private final String fieldName; private final String fieldName;
private final BinaryCodec binaryCodec;//spatial4j private final BinaryCodec binaryCodec;//spatial4j
@ -195,65 +177,21 @@ public class SerializedDVStrategy extends SpatialStrategy {
} }
@Override @Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
final BinaryDocValues docValues = readerContext.reader().getBinaryDocValues(fieldName); final BinaryDocValues docValues = readerContext.reader().getBinaryDocValues(fieldName);
return new FunctionValues() { return new ShapeValues() {
int bytesRefDoc = -1; @Override
BytesRefBuilder bytesRef = new BytesRefBuilder(); public boolean advanceExact(int doc) throws IOException {
return docValues.advanceExact(doc);
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;
} }
@Override @Override
public boolean exists(int doc) throws IOException { public Shape value() throws IOException {
return fillBytes(doc); BytesRef bytesRef = docValues.binaryValue();
} DataInputStream dataInput
= new DataInputStream(new ByteArrayInputStream(bytesRef.bytes, bytesRef.offset, bytesRef.length));
@Override return binaryCodec.readShape(dataInput);
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?
} }
}; };
@ -278,7 +216,7 @@ public class SerializedDVStrategy extends SpatialStrategy {
} }
@Override @Override
public String description() { public String toString() {
return "shapeDocVal(" + fieldName + ")"; return "shapeDocVal(" + fieldName + ")";
} }
}//ShapeDocValueSource }//ShapeDocValueSource

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,18 +16,30 @@
*/ */
package org.apache.lucene.spatial.vector; 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.DoubleDocValuesField;
import org.apache.lucene.document.DoublePoint; import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType; import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.queries.function.FunctionRangeQuery; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery; 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.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.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.spatial.query.SpatialOperation;
@ -169,12 +181,12 @@ public class PointVectorStrategy extends SpatialStrategy {
} }
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) { public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
return new DistanceValueSource(this, queryPoint, multiplier); return new DistanceValueSource(this, queryPoint, multiplier);
} }
@Override @Override
public ConstantScoreQuery makeQuery(SpatialArgs args) { public Query makeQuery(SpatialArgs args) {
if(! SpatialOperation.is( args.getOperation(), if(! SpatialOperation.is( args.getOperation(),
SpatialOperation.Intersects, SpatialOperation.Intersects,
SpatialOperation.IsWithin )) SpatialOperation.IsWithin ))
@ -186,13 +198,7 @@ public class PointVectorStrategy extends SpatialStrategy {
} else if (shape instanceof Circle) { } else if (shape instanceof Circle) {
Circle circle = (Circle)shape; Circle circle = (Circle)shape;
Rectangle bbox = circle.getBoundingBox(); Rectangle bbox = circle.getBoundingBox();
Query approxQuery = makeWithin(bbox); return new DistanceRangeQuery(makeWithin(bbox), makeDistanceValueSource(circle.getCenter()), circle.getRadius());
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());
} else { } else {
throw new UnsupportedOperationException("Only Rectangles and Circles are currently supported, " + throw new UnsupportedOperationException("Only Rectangles and Circles are currently supported, " +
"found [" + shape.getClass() + "]");//TODO "found [" + shape.getClass() + "]");//TODO
@ -237,4 +243,71 @@ public class PointVectorStrategy extends SpatialStrategy {
//TODO try doc-value range query? //TODO try doc-value range query?
throw new UnsupportedOperationException("An index is required for this operation."); 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("100", ctx.makePoint(2, 1));
adoc("101", ctx.makePoint(-1, 4)); adoc("101", ctx.makePoint(-1, 4));
adoc("103", (Shape)null);//test score for nothing adoc("103", (Shape)null);//test score for nothing
adoc("999", ctx.makePoint(2, 1));//test deleted
commit();
deleteDoc("999");
commit(); commit();
//FYI distances are in docid order //FYI distances are in docid order
checkDistValueSource(ctx.makePoint(4, 3), 2.8274937f, 5.0898066f, 180f); 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); Point p101 = ctx.makePoint(-1.001, 4.001);
adoc("101", p101); adoc("101", p101);
adoc("103", (Shape)null);//test score for nothing adoc("103", (Shape)null);//test score for nothing
adoc("999", ctx.makePoint(2, 1));//test deleted
commit();
deleteDoc("999");
commit(); commit();
double dist = ctx.getDistCalc().distance(p100, p101); double dist = ctx.getDistCalc().distance(p100, p101);

View File

@ -18,10 +18,6 @@ package org.apache.lucene.spatial;
import java.io.IOException; 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.Document;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericDocValuesField; 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.IndexReader;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig; 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.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query; 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.Directory;
import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.LuceneTestCase; 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 * 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 //--Match all, order by distance ascending
{ {
Point pt = ctx.makePoint(60, -50); 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 Sort distSort = new Sort(valueSource.getSortField(false)).rewrite(indexSearcher);//false=asc dist
TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort); TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort);
assertDocMatchedIds(indexSearcher, docs, 4, 20, 2); assertDocMatchedIds(indexSearcher, docs, 4, 20, 2);

View File

@ -29,23 +29,21 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger; 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.Document;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField; import org.apache.lucene.document.StringField;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.queries.function.FunctionQuery; import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.CheckHits;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery; 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.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialArgsParser; import org.apache.lucene.spatial.query.SpatialArgsParser;
import org.apache.lucene.spatial.query.SpatialOperation; 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 { public abstract class StrategyTestCase extends SpatialTestCase {
@ -212,25 +210,18 @@ public abstract class StrategyTestCase extends SpatialTestCase {
} }
/** scores[] are in docId order */ /** scores[] are in docId order */
protected void checkValueSource(ValueSource vs, float scores[], float delta) throws IOException { protected void checkValueSource(DoubleValuesSource vs, float scores[], float delta) throws IOException {
FunctionQuery q = new FunctionQuery(vs);
// //TODO is there any point to this check? for (LeafReaderContext ctx : indexSearcher.getTopReaderContext().leaves()) {
// int expectedDocs[] = new int[scores.length];//fill with ascending 0....length-1 DoubleValues v = vs.getValues(ctx, null);
// for (int i = 0; i < expectedDocs.length; i++) { int count = ctx.reader().maxDoc();
// expectedDocs[i] = i; for (int i = 0; i < count; i++) {
// } assertTrue(v.advanceExact(i));
// CheckHits.checkHits(random(), q, "", indexSearcher, expectedDocs); int doc = i + ctx.docBase;
assertEquals("Not equal for doc " + doc, v.doubleValue(), (double) scores[doc], delta);
//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);
} }
CheckHits.checkExplanations(q, "", indexSearcher);
} }
protected void testOperation(Shape indexedShape, SpatialOperation operation, 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.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy; import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory; 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.GeoPathFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint; import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory; import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoShape; import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.PlanetModel; import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.junit.Test; import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext; 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); 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) { public Geo3dRectIntersectionTestHelper(SpatialContext ctx) {
super(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.DocValuesType;
import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermQuery;
import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource; import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource;
import org.apache.lucene.spatial.query.SpatialArgs; 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 * Provides access to each rectangle per document
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
* Shape}.
*/ //TODO raise to SpatialStrategy */ //TODO raise to SpatialStrategy
public ValueSource makeShapeValueSource() { public ShapeValuesSource makeShapeValueSource() {
return new BBoxValueSource(this); return new BBoxValueSource(this);
} }
@Override @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. //TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx); return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
} }
/** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a /** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a
* convenience method. */ * convenience method. */
public ValueSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) { public DoubleValuesSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
return new BBoxOverlapRatioValueSource( return new BBoxOverlapRatioValueSource(
makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0); makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0);
} }

View File

@ -17,16 +17,14 @@
package org.apache.solr.legacy; package org.apache.solr.legacy;
import java.io.IOException; 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.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.queries.function.FunctionValues; import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.search.Explanation; import org.apache.lucene.spatial.ShapeValuesSource;
import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/** /**
* A ValueSource in which the indexed Rectangle is returned from * A ValueSource in which the indexed Rectangle is returned from
@ -34,7 +32,7 @@ import org.locationtech.spatial4j.shape.Rectangle;
* *
* @lucene.internal * @lucene.internal
*/ */
class BBoxValueSource extends ValueSource { class BBoxValueSource extends ShapeValuesSource {
private final BBoxStrategy strategy; private final BBoxStrategy strategy;
@ -43,76 +41,34 @@ class BBoxValueSource extends ValueSource {
} }
@Override @Override
public String description() { public String toString() {
return "bboxShape(" + strategy.getFieldName() + ")"; return "bboxShape(" + strategy.getFieldName() + ")";
} }
@Override @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 DoubleValues minX = DoubleValuesSource.fromDoubleField(strategy.field_minX).getValues(readerContext, null);
final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY); final DoubleValues minY = DoubleValuesSource.fromDoubleField(strategy.field_minY).getValues(readerContext, null);
final NumericDocValues maxX = DocValues.getNumeric(reader, strategy.field_maxX); final DoubleValues maxX = DoubleValuesSource.fromDoubleField(strategy.field_maxX).getValues(readerContext, null);
final NumericDocValues maxY = DocValues.getNumeric(reader, strategy.field_maxY); final DoubleValues maxY = DoubleValuesSource.fromDoubleField(strategy.field_maxY).getValues(readerContext, null);
//reused //reused
final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0); final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
return new FunctionValues() { return new ShapeValues() {
private int lastDocID = -1;
private double getDocValue(NumericDocValues values, int doc) throws IOException { @Override
int curDocID = values.docID(); public boolean advanceExact(int doc) throws IOException {
if (doc > curDocID) { return minX.advanceExact(doc) && maxX.advanceExact(doc) && minY.advanceExact(doc) && maxY.advanceExact(doc);
curDocID = values.advance(doc);
}
if (doc == curDocID) {
return Double.longBitsToDouble(values.longValue());
} else {
return 0.0;
}
} }
@Override @Override
public Object objectVal(int doc) throws IOException { public Shape value() throws IOException {
if (doc < lastDocID) { rect.reset(minX.doubleValue(), maxX.doubleValue(), minY.doubleValue(), maxY.doubleValue());
throw new AssertionError("docs were sent out-of-order: lastDocID=" + lastDocID + " vs doc=" + doc); return rect;
}
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;
}
} }
@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; 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.distance.DistanceCalculator;
import org.locationtech.spatial4j.shape.Point; 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 * An implementation of the Lucene ValueSource model that returns the distance
@ -34,11 +30,13 @@ import java.util.Map;
* *
* @lucene.internal * @lucene.internal
*/ */
public class DistanceValueSource extends ValueSource { public class DistanceValueSource extends DoubleValuesSource {
private PointVectorStrategy strategy; private PointVectorStrategy strategy;
private final Point from; private final Point from;
private final double multiplier; private final double multiplier;
private final double nullValue;
/** /**
* Constructor. * Constructor.
@ -47,13 +45,15 @@ public class DistanceValueSource extends ValueSource {
this.strategy = strategy; this.strategy = strategy;
this.from = from; this.from = from;
this.multiplier = multiplier; this.multiplier = multiplier;
this.nullValue =
(strategy.getSpatialContext().isGeo() ? 180 * multiplier : Double.MAX_VALUE);
} }
/** /**
* Returns the ValueSource description. * Returns the ValueSource description.
*/ */
@Override @Override
public String description() { public String toString() {
return "DistanceValueSource("+strategy+", "+from+")"; return "DistanceValueSource("+strategy+", "+from+")";
} }
@ -61,55 +61,30 @@ public class DistanceValueSource extends ValueSource {
* Returns the FunctionValues used by the function query. * Returns the FunctionValues used by the function query.
*/ */
@Override @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 DoubleValues ptX = DoubleValuesSource.fromDoubleField(strategy.getFieldNameX()).getValues(readerContext, null);
final NumericDocValues ptY = DocValues.getNumeric(reader, strategy.getFieldNameY()); 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; @Override
public double doubleValue() throws IOException {
private final Point from = DistanceValueSource.this.from; return calculator.distance(from, ptX.doubleValue(), ptY.doubleValue()) * multiplier;
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 @Override
public float floatVal(int doc) throws IOException { public boolean advanceExact(int doc) throws IOException {
return (float) doubleVal(doc); 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 @Override
public String toString(int doc) throws IOException { public boolean needsScores() {
return description() + "=" + floatVal(doc); return false;
}
};
} }
@Override @Override

View File

@ -23,15 +23,11 @@ import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexOptions;
import org.apache.solr.legacy.LegacyDoubleField; import org.apache.lucene.queries.function.FunctionMatchQuery;
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.search.BooleanClause; import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialArgs;
@ -218,7 +214,7 @@ public class PointVectorStrategy extends SpatialStrategy {
} }
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) { public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
return new DistanceValueSource(this, queryPoint, multiplier); return new DistanceValueSource(this, queryPoint, multiplier);
} }
@ -237,10 +233,11 @@ public class PointVectorStrategy extends SpatialStrategy {
Rectangle bbox = circle.getBoundingBox(); Rectangle bbox = circle.getBoundingBox();
Query approxQuery = makeWithin(bbox); Query approxQuery = makeWithin(bbox);
BooleanQuery.Builder bqBuilder = new BooleanQuery.Builder(); BooleanQuery.Builder bqBuilder = new BooleanQuery.Builder();
FunctionRangeQuery vsRangeQuery = double r = circle.getRadius();
new FunctionRangeQuery(makeDistanceValueSource(circle.getCenter()), 0.0, circle.getRadius(), true, true); 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(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()); return new ConstantScoreQuery(bqBuilder.build());
} else { } else {
throw new UnsupportedOperationException("Only Rectangles and Circles are currently supported, " + 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 java.util.Iterator;
import org.apache.lucene.index.IndexableField; 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.queries.function.ValueSource;
import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.search.Query; import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy; import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy; 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.response.QueryResponseWriter;
import org.apache.solr.schema.AbstractSpatialFieldType; import org.apache.solr.schema.AbstractSpatialFieldType;
import org.apache.solr.schema.SchemaField; 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.GeoJSONWriter;
import org.locationtech.spatial4j.io.ShapeWriter; import org.locationtech.spatial4j.io.ShapeWriter;
import org.locationtech.spatial4j.io.SupportedFormats; import org.locationtech.spatial4j.io.SupportedFormats;
@ -87,7 +87,7 @@ public class GeoTransformerFactory extends TransformerFactory
updater.display = display; updater.display = display;
updater.display_error = display+"_error"; updater.display_error = display+"_error";
ValueSource shapes = null; final ShapeValuesSource shapes;
AbstractSpatialFieldType<?> sdv = (AbstractSpatialFieldType<?>)sf.getType(); AbstractSpatialFieldType<?> sdv = (AbstractSpatialFieldType<?>)sf.getType();
SpatialStrategy strategy = sdv.getStrategy(fname); SpatialStrategy strategy = sdv.getStrategy(fname);
if(strategy instanceof CompositeSpatialStrategy) { if(strategy instanceof CompositeSpatialStrategy) {
@ -98,6 +98,8 @@ public class GeoTransformerFactory extends TransformerFactory
shapes = ((SerializedDVStrategy)strategy) shapes = ((SerializedDVStrategy)strategy)
.makeShapeValueSource(); .makeShapeValueSource();
} }
else
shapes = null;
String writerName = params.get("w", "GeoJSON"); String writerName = params.get("w", "GeoJSON");
@ -122,20 +124,24 @@ public class GeoTransformerFactory extends TransformerFactory
// Using ValueSource // Using ValueSource
if(shapes!=null) { if(shapes!=null) {
// we don't really need the qparser... just so we can reuse valueSource return new DocTransformer() {
QParser parser = new QParser(null,null,params, req) {
@Override @Override
public Query parse() throws SyntaxError { public String getName() {
return new MatchAllDocsQuery(); return display;
} }
};
return new ValueSourceAugmenter(display, parser, shapes) {
@Override @Override
protected void setValue(SolrDocument doc, Object val) { public void transform(SolrDocument doc, int docid, float score) throws IOException {
updater.setValue(doc, val); 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 // Using the raw stored values
@ -160,6 +166,7 @@ public class GeoTransformerFactory extends TransformerFactory
} }
}; };
} }
} }
class GeoFieldUpdater { 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.Field;
import org.apache.lucene.document.StoredField; import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexableField; 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.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery; 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.Query;
import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial.SpatialStrategy; 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)); String scoreParam = (localParams == null ? null : localParams.get(SCORE_PARAM));
//We get the valueSource for the score then the filter and combine them. //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) { if (valueSource == null) {
return strategy.makeQuery(spatialArgs); //assumed constant scoring 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)) if (localParams != null && !localParams.getBool(FILTER_PARAM, true))
return functionQuery; return functionQuery;
@ -383,7 +385,7 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
return supportedScoreModes; 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) { if (score == null) {
return null; return null;
} }

View File

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

View File

@ -18,16 +18,15 @@
package org.apache.solr.schema; package org.apache.solr.schema;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.LatLonDocValuesField; import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.document.LatLonPoint; import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.index.LeafReaderContext; 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.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.FieldComparator;
import org.apache.lucene.search.IndexOrDocValuesQuery; import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.LeafFieldComparator; import org.apache.lucene.search.LeafFieldComparator;
@ -177,7 +176,7 @@ public class LatLonPointSpatialField extends AbstractSpatialFieldType implements
} }
@Override @Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) { public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
if (!docValues) { if (!docValues) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
getFieldName() + " must have docValues enabled to support this feature"); 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)}. * 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 String fieldName;
private final Point queryPoint; private final Point queryPoint;
private final double multiplier; private final double multiplier;
@ -218,41 +217,38 @@ public class LatLonPointSpatialField extends AbstractSpatialFieldType implements
} }
@Override @Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
return new DoubleDocValues(this) { return new DoubleValues() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final FieldComparator<Double> comparator = final FieldComparator<Double> comparator =
(FieldComparator<Double>) getSortField(false).getComparator(1, 1); (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 final double mult = multiplier; // so it's a local field
// Since this computation is expensive, it's worth caching it just in case. double value = Double.POSITIVE_INFINITY;
double cacheDoc = -1;
double cacheVal = Double.POSITIVE_INFINITY;
@Override @Override
public double doubleVal(int doc) { public double doubleValue() throws IOException {
if (cacheDoc != doc) { return value;
try {
leafComparator.copy(0, doc);
cacheVal = comparator.value(0) * mult;
cacheDoc = doc;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return cacheVal;
} }
@Override @Override
public boolean exists(int doc) { public boolean advanceExact(int doc) throws IOException {
return !Double.isInfinite(doubleVal(doc)); leafComparator.copy(0, doc);
value = comparator.value(0) * mult;
return true;
} }
}; };
} }
@Override @Override
public String description() { public boolean needsScores() {
return false;
}
@Override
public String toString() {
return "distSort(" + fieldName + ", " + queryPoint + ", mult:" + multiplier + ")"; 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.analysis.Analyzer;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues; import org.apache.lucene.spatial.ShapeValues;
import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.spatial.ShapeValuesSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy; import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.query.SpatialArgsParser; import org.apache.lucene.spatial.query.SpatialArgsParser;
@ -103,24 +102,24 @@ public class RptWithGeometrySpatialField extends AbstractSpatialFieldType<Compos
} }
@Override @Override
public ValueSource makeShapeValueSource() { public ShapeValuesSource makeShapeValueSource() {
return new CachingShapeValuesource(super.makeShapeValueSource(), getFieldName()); 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 final String fieldName;
private CachingShapeValuesource(ValueSource targetValueSource, String fieldName) { private CachingShapeValuesource(ShapeValuesSource targetValueSource, String fieldName) {
this.targetValueSource = targetValueSource; this.targetValueSource = targetValueSource;
this.fieldName = fieldName; this.fieldName = fieldName;
} }
@Override @Override
public String description() { public String toString() {
return "cache(" + targetValueSource.description() + ")"; return "cache(" + targetValueSource.toString() + ")";
} }
@Override @Override
@ -142,10 +141,9 @@ public class RptWithGeometrySpatialField extends AbstractSpatialFieldType<Compos
return result; return result;
} }
@SuppressWarnings("unchecked")
@Override @Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { public ShapeValues getValues(LeafReaderContext readerContext) throws IOException {
final FunctionValues targetFuncValues = targetValueSource.getValues(context, readerContext); 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. // 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 = final SolrCache<PerSegCacheKey,Shape> cache =
SolrRequestInfo.getRequestInfo().getReq().getSearcher().getCache(CACHE_KEY_PREFIX + fieldName); 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 targetFuncValues; // no caching; no configured cache
} }
return new FunctionValues() { return new ShapeValues() {
int docId = -1; int docId = -1;
Shape shape = null;
private void setShapeFromDoc(int doc) throws IOException { @Override
if (docId == doc) { public Shape value() throws IOException {
return;
}
docId = doc;
//lookup in cache //lookup in cache
IndexReader.CacheHelper cacheHelper = readerContext.reader().getCoreCacheHelper(); IndexReader.CacheHelper cacheHelper = readerContext.reader().getCoreCacheHelper();
if (cacheHelper == null) { if (cacheHelper == null) {
throw new IllegalStateException("Leaf " + readerContext.reader() + " is not suited for caching"); throw new IllegalStateException("Leaf " + readerContext.reader() + " is not suited for caching");
} }
PerSegCacheKey key = new PerSegCacheKey(cacheHelper.getKey(), doc); PerSegCacheKey key = new PerSegCacheKey(cacheHelper.getKey(), docId);
shape = cache.get(key); Shape shape = cache.get(key);
if (shape == null) { if (shape == null) {
shape = (Shape) targetFuncValues.objectVal(doc); shape = targetFuncValues.value();
if (shape != null) { if (shape != null) {
cache.put(key, shape); 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 ((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; return shape;
} }
@Override @Override
public Explanation explain(int doc) throws IOException { public boolean advanceExact(int doc) throws IOException {
return targetFuncValues.explain(doc); 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.Collections;
import java.util.List; 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;
import org.apache.lucene.queries.function.valuesource.ConstNumberSource; import org.apache.lucene.queries.function.valuesource.ConstNumberSource;
import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource; 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.search.ValueSourceParser;
import org.apache.solr.util.DistanceUnits; import org.apache.solr.util.DistanceUnits;
import org.apache.solr.util.SpatialUtils; 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} * Parses "geodist" creating {@link HaversineConstFunction} or {@link HaversineFunction}
@ -135,7 +135,7 @@ public class GeoDistValueSourceParser extends ValueSourceParser {
SpatialStrategy strategy = ((SpatialStrategyMultiValueSource) mv2).strategy; SpatialStrategy strategy = ((SpatialStrategyMultiValueSource) mv2).strategy;
DistanceUnits distanceUnits = ((SpatialStrategyMultiValueSource) mv2).distanceUnits; DistanceUnits distanceUnits = ((SpatialStrategyMultiValueSource) mv2).distanceUnits;
Point queryPoint = strategy.getSpatialContext().makePoint(constants[1], constants[0]); 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) { if (constants != null && other instanceof VectorValueSource) {

View File

@ -20,14 +20,10 @@ import java.text.ParseException;
import java.util.Arrays; import java.util.Arrays;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; 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.SolrTestCaseJ4;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.core.SolrCore; import org.apache.solr.core.SolrCore;
import org.apache.solr.legacy.BBoxStrategy;
import org.apache.solr.schema.BBoxField; import org.apache.solr.schema.BBoxField;
import org.apache.solr.schema.IndexSchema; import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField; import org.apache.solr.schema.SchemaField;
@ -35,6 +31,10 @@ import org.apache.solr.util.SpatialUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; 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 * Test Solr 4's new spatial capabilities from the new Lucene spatial module. Don't thoroughly test it here because