* LUCENE-7094: BBoxStrategy and PointVectorStrategy now support PointValues (in addition to legacy numeric trie). Their APIs were changed a little and also made more consistent. PointValues/Trie is optional, DocValues is optional, stored value is optional.

This commit is contained in:
nknize 2016-03-30 18:14:20 -05:00
parent 5e5fd66257
commit e1b45568b4
14 changed files with 531 additions and 272 deletions

View File

@ -118,6 +118,12 @@ New Features
API Changes
* LUCENE-7094: BBoxStrategy and PointVectorStrategy now support
PointValues (in addition to legacy numeric trie). Their APIs
were changed a little and also made more consistent. PointValues/Trie
is optional, DocValues is optional, stored value is optional.
(Nick Knize, David Smiley)
* LUCENE-6067: Accountable.getChildResources has a default
implementation returning the empty list. (Robert Muir)

View File

@ -16,15 +16,15 @@
*/
package org.apache.lucene.spatial.bbox;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.document.LegacyDoubleField;
import org.apache.lucene.document.DoubleDocValuesField;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.LegacyDoubleField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause;
@ -41,6 +41,10 @@ import org.apache.lucene.spatial.util.DistanceToShapeValueSource;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.LegacyNumericUtils;
import org.apache.lucene.util.NumericUtils;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/**
@ -63,7 +67,7 @@ import org.apache.lucene.util.NumericUtils;
* <p>
* This uses 4 double fields for minX, maxX, minY, maxY
* and a boolean to mark a dateline cross. Depending on the particular {@link
* SpatialOperation}s, there are a variety of {@link org.apache.lucene.search.LegacyNumericRangeQuery}s to be
* SpatialOperation}s, there are a variety of range queries on {@link DoublePoint}s to be
* done.
* The {@link #makeOverlapRatioValueSource(org.locationtech.spatial4j.shape.Rectangle, double)}
* works by calculating the query bbox overlap percentage against the indexed
@ -74,6 +78,35 @@ import org.apache.lucene.util.NumericUtils;
*/
public class BBoxStrategy extends SpatialStrategy {
// note: we use a FieldType to articulate the options we want on the field. We don't use it as-is with a Field, we
// create more than one Field.
/**
* pointValues, docValues, and nothing else.
*/
public static FieldType DEFAULT_FIELDTYPE;
@Deprecated
public static FieldType LEGACY_FIELDTYPE;
static {
// Default: pointValues + docValues
FieldType type = new FieldType();
type.setDimensions(1, Double.BYTES);//pointValues (assume Double)
type.setDocValuesType(DocValuesType.NUMERIC);//docValues
type.setStored(false);
type.freeze();
DEFAULT_FIELDTYPE = type;
// Legacy default: legacyNumerics + docValues
type = new FieldType();
type.setIndexOptions(IndexOptions.DOCS);
type.setNumericType(FieldType.LegacyNumericType.DOUBLE);
type.setNumericPrecisionStep(8);// same as solr default
type.setDocValuesType(DocValuesType.NUMERIC);//docValues
type.setStored(false);
type.freeze();
LEGACY_FIELDTYPE = type;
}
public static final String SUFFIX_MINX = "__minX";
public static final String SUFFIX_MAXX = "__maxX";
public static final String SUFFIX_MINY = "__minY";
@ -84,17 +117,45 @@ public class BBoxStrategy extends SpatialStrategy {
* The Bounding Box gets stored as four fields for x/y min/max and a flag
* that says if the box crosses the dateline (xdl).
*/
protected final String field_bbox;
protected final String field_minX;
protected final String field_minY;
protected final String field_maxX;
protected final String field_maxY;
protected final String field_xdl; // crosses dateline
final String field_bbox;
final String field_minX;
final String field_minY;
final String field_maxX;
final String field_maxY;
final String field_xdl; // crosses dateline
protected FieldType fieldType;//for the 4 numbers
protected FieldType xdlFieldType;
private final FieldType optionsFieldType;//from constructor; aggregate field type used to express all options
private final int fieldsLen;
private final boolean hasStored;
private final boolean hasDocVals;
private final boolean hasPointVals;
// equiv to "hasLegacyNumerics":
private final FieldType legacyNumericFieldType; // not stored; holds precision step.
private final FieldType xdlFieldType;
public BBoxStrategy(SpatialContext ctx, String fieldNamePrefix) {
/**
* Creates a new {@link BBoxStrategy} instance that uses {@link DoublePoint} and {@link DoublePoint#newRangeQuery}
*/
public static BBoxStrategy newInstance(SpatialContext ctx, String fieldNamePrefix) {
return new BBoxStrategy(ctx, fieldNamePrefix, DEFAULT_FIELDTYPE);
}
/**
* Creates a new {@link BBoxStrategy} instance that uses {@link LegacyDoubleField} for backwards compatibility
* @deprecated LegacyNumerics will be removed
*/
@Deprecated
public static BBoxStrategy newLegacyInstance(SpatialContext ctx, String fieldNamePrefix) {
return new BBoxStrategy(ctx, fieldNamePrefix, LEGACY_FIELDTYPE);
}
/**
* Creates this strategy.
* {@code fieldType} is used to customize the indexing options of the 4 number fields, and to a lesser degree the XDL
* field too. Search requires pointValues (or legacy numerics), and relevancy requires docValues. If these features
* aren't needed then disable them.
*/
public BBoxStrategy(SpatialContext ctx, String fieldNamePrefix, FieldType fieldType) {
super(ctx, fieldNamePrefix);
field_bbox = fieldNamePrefix;
field_minX = fieldNamePrefix + SUFFIX_MINX;
@ -103,34 +164,49 @@ public class BBoxStrategy extends SpatialStrategy {
field_maxY = fieldNamePrefix + SUFFIX_MAXY;
field_xdl = fieldNamePrefix + SUFFIX_XDL;
FieldType fieldType = new FieldType(LegacyDoubleField.TYPE_NOT_STORED);
fieldType.setNumericPrecisionStep(8);//Solr's default
fieldType.setDocValuesType(DocValuesType.NUMERIC);
setFieldType(fieldType);
}
private int getPrecisionStep() {
return fieldType.numericPrecisionStep();
}
public FieldType getFieldType() {
return fieldType;
}
/** Used to customize the indexing options of the 4 number fields, and to a lesser degree the XDL field too. Search
* requires indexed=true, and relevancy requires docValues. If these features aren't needed then disable them.
* {@link FieldType#freeze()} is called on the argument. */
public void setFieldType(FieldType fieldType) {
fieldType.freeze();
this.fieldType = fieldType;
//only double's supported right now
if (fieldType.numericType() != FieldType.LegacyNumericType.DOUBLE)
throw new IllegalArgumentException("BBoxStrategy only supports doubles at this time.");
//for xdlFieldType, copy some similar options. Don't do docValues since it isn't needed here.
xdlFieldType = new FieldType(StringField.TYPE_NOT_STORED);
xdlFieldType.setStored(fieldType.stored());
xdlFieldType.setIndexOptions(fieldType.indexOptions());
xdlFieldType.freeze();
this.optionsFieldType = fieldType;
int numQuads = 0;
if ((this.hasStored = fieldType.stored())) {
numQuads++;
}
if ((this.hasDocVals = fieldType.docValuesType() != DocValuesType.NONE)) {
numQuads++;
}
if ((this.hasPointVals = fieldType.pointDimensionCount() > 0)) {
numQuads++;
}
if (fieldType.indexOptions() != IndexOptions.NONE && fieldType.numericType() != null) {
if (hasPointVals) {
throw new IllegalArgumentException("pointValues and LegacyNumericType are mutually exclusive");
}
if (fieldType.numericType() != FieldType.LegacyNumericType.DOUBLE) {
throw new IllegalArgumentException(getClass() + " does not support " + fieldType.numericType());
}
numQuads++;
legacyNumericFieldType = new FieldType(LegacyDoubleField.TYPE_NOT_STORED);
legacyNumericFieldType.setNumericPrecisionStep(fieldType.numericPrecisionStep());
legacyNumericFieldType.freeze();
} else {
legacyNumericFieldType = null;
}
if (hasPointVals || legacyNumericFieldType != null) { // if we have an index...
xdlFieldType = new FieldType(StringField.TYPE_NOT_STORED);
xdlFieldType.setIndexOptions(IndexOptions.DOCS);
xdlFieldType.freeze();
} else {
xdlFieldType = null;
}
this.fieldsLen = numQuads * 4 + (xdlFieldType != null ? 1 : 0);
}
/** Returns a field type representing the set of field options. This is identical to what was passed into the
* constructor. It's frozen. */
public FieldType getFieldType() {
return optionsFieldType;
}
//---------------------------------
@ -142,39 +218,40 @@ public class BBoxStrategy extends SpatialStrategy {
return createIndexableFields(shape.getBoundingBox());
}
public Field[] createIndexableFields(Rectangle bbox) {
Field[] fields = new Field[5];
fields[0] = new ComboField(field_minX, bbox.getMinX(), fieldType);
fields[1] = new ComboField(field_maxX, bbox.getMaxX(), fieldType);
fields[2] = new ComboField(field_minY, bbox.getMinY(), fieldType);
fields[3] = new ComboField(field_maxY, bbox.getMaxY(), fieldType);
fields[4] = new ComboField(field_xdl, bbox.getCrossesDateLine()?"T":"F", xdlFieldType);
private Field[] createIndexableFields(Rectangle bbox) {
Field[] fields = new Field[fieldsLen];
int idx = -1;
if (hasStored) {
fields[++idx] = new StoredField(field_minX, bbox.getMinX());
fields[++idx] = new StoredField(field_minY, bbox.getMinY());
fields[++idx] = new StoredField(field_maxX, bbox.getMaxX());
fields[++idx] = new StoredField(field_maxY, bbox.getMaxY());
}
if (hasDocVals) {
fields[++idx] = new DoubleDocValuesField(field_minX, bbox.getMinX());
fields[++idx] = new DoubleDocValuesField(field_minY, bbox.getMinY());
fields[++idx] = new DoubleDocValuesField(field_maxX, bbox.getMaxX());
fields[++idx] = new DoubleDocValuesField(field_maxY, bbox.getMaxY());
}
if (hasPointVals) {
fields[++idx] = new DoublePoint(field_minX, bbox.getMinX());
fields[++idx] = new DoublePoint(field_minY, bbox.getMinY());
fields[++idx] = new DoublePoint(field_maxX, bbox.getMaxX());
fields[++idx] = new DoublePoint(field_maxY, bbox.getMaxY());
}
if (legacyNumericFieldType != null) {
fields[++idx] = new LegacyDoubleField(field_minX, bbox.getMinX(), legacyNumericFieldType);
fields[++idx] = new LegacyDoubleField(field_minY, bbox.getMinY(), legacyNumericFieldType);
fields[++idx] = new LegacyDoubleField(field_maxX, bbox.getMaxX(), legacyNumericFieldType);
fields[++idx] = new LegacyDoubleField(field_maxY, bbox.getMaxY(), legacyNumericFieldType);
}
if (xdlFieldType != null) {
fields[++idx] = new Field(field_xdl, bbox.getCrossesDateLine()?"T":"F", xdlFieldType);
}
assert idx == fields.length - 1;
return fields;
}
/** Field subclass circumventing Field limitations. This one instance can have any combination of indexed, stored,
* and docValues.
*/
private static class ComboField extends Field {
private ComboField(String name, Object value, FieldType type) {
super(name, type);//this expert constructor allows us to have a field that has docValues & indexed/stored
super.fieldsData = value;
}
//Is this a hack? We assume that numericValue() is only called for DocValues purposes.
@Override
public Number numericValue() {
//Numeric DocValues only supports Long,
final Number number = super.numericValue();
if (number == null)
return null;
if (fieldType().numericType() == FieldType.LegacyNumericType.DOUBLE)
return Double.doubleToLongBits(number.doubleValue());
if (fieldType().numericType() == FieldType.LegacyNumericType.FLOAT)
return Float.floatToIntBits(number.floatValue());
return number.longValue();
}
}
//---------------------------------
// Value Source / Relevancy
@ -253,8 +330,8 @@ public class BBoxStrategy extends SpatialStrategy {
// Y conditions
// docMinY <= queryExtent.getMinY() AND docMaxY >= queryExtent.getMaxY()
Query qMinY = LegacyNumericRangeQuery.newDoubleRange(field_minY, getPrecisionStep(), null, bbox.getMinY(), false, true);
Query qMaxY = LegacyNumericRangeQuery.newDoubleRange(field_maxY, getPrecisionStep(), bbox.getMaxY(), null, true, false);
Query qMinY = this.makeNumericRangeQuery(field_minY, null, bbox.getMinY(), false, true);
Query qMaxY = this.makeNumericRangeQuery(field_maxY, bbox.getMaxY(), null, true, false);
Query yConditions = this.makeQuery(BooleanClause.Occur.MUST, qMinY, qMaxY);
// X conditions
@ -266,8 +343,8 @@ public class BBoxStrategy extends SpatialStrategy {
// X Conditions for documents that do not cross the date line,
// documents that contain the min X and max X of the query envelope,
// docMinX <= queryExtent.getMinX() AND docMaxX >= queryExtent.getMaxX()
Query qMinX = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), null, bbox.getMinX(), false, true);
Query qMaxX = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), bbox.getMaxX(), null, true, false);
Query qMinX = this.makeNumericRangeQuery(field_minX, null, bbox.getMinX(), false, true);
Query qMaxX = this.makeNumericRangeQuery(field_maxX, bbox.getMaxX(), null, true, false);
Query qMinMax = this.makeQuery(BooleanClause.Occur.MUST, qMinX, qMaxX);
Query qNonXDL = this.makeXDL(false, qMinMax);
@ -278,8 +355,8 @@ public class BBoxStrategy extends SpatialStrategy {
// the left portion of the document contains the min X of the query
// OR the right portion of the document contains the max X of the query,
// docMinXLeft <= queryExtent.getMinX() OR docMaxXRight >= queryExtent.getMaxX()
Query qXDLLeft = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), null, bbox.getMinX(), false, true);
Query qXDLRight = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), bbox.getMaxX(), null, true, false);
Query qXDLLeft = this.makeNumericRangeQuery(field_minX, null, bbox.getMinX(), false, true);
Query qXDLRight = this.makeNumericRangeQuery(field_maxX, bbox.getMaxX(), null, true, false);
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.SHOULD, qXDLLeft, qXDLRight);
Query qXDL = this.makeXDL(true, qXDLLeftRight);
@ -302,8 +379,8 @@ public class BBoxStrategy extends SpatialStrategy {
// the left portion of the document contains the min X of the query
// AND the right portion of the document contains the max X of the query,
// docMinXLeft <= queryExtent.getMinX() AND docMaxXRight >= queryExtent.getMaxX()
Query qXDLLeft = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), null, bbox.getMinX(), false, true);
Query qXDLRight = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), bbox.getMaxX(), null, true, false);
Query qXDLLeft = this.makeNumericRangeQuery(field_minX, null, bbox.getMinX(), false, true);
Query qXDLRight = this.makeNumericRangeQuery(field_maxX, bbox.getMaxX(), null, true, false);
Query qXDLLeftRight = this.makeXDL(true, this.makeQuery(BooleanClause.Occur.MUST, qXDLLeft, qXDLRight));
Query qWorld = makeQuery(BooleanClause.Occur.MUST,
@ -328,8 +405,8 @@ public class BBoxStrategy extends SpatialStrategy {
// Y conditions
// docMinY > queryExtent.getMaxY() OR docMaxY < queryExtent.getMinY()
Query qMinY = LegacyNumericRangeQuery.newDoubleRange(field_minY, getPrecisionStep(), bbox.getMaxY(), null, false, false);
Query qMaxY = LegacyNumericRangeQuery.newDoubleRange(field_maxY, getPrecisionStep(), null, bbox.getMinY(), false, false);
Query qMinY = this.makeNumericRangeQuery(field_minY, bbox.getMaxY(), null, false, false);
Query qMaxY = this.makeNumericRangeQuery(field_maxY, null, bbox.getMinY(), false, false);
Query yConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qMinY, qMaxY);
// X conditions
@ -340,14 +417,15 @@ public class BBoxStrategy extends SpatialStrategy {
// X Conditions for documents that do not cross the date line,
// docMinX > queryExtent.getMaxX() OR docMaxX < queryExtent.getMinX()
Query qMinX = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMaxX(), null, false, false);
Query qMinX = this.makeNumericRangeQuery(field_minX, bbox.getMaxX(), null, false, false);
if (bbox.getMinX() == -180.0 && ctx.isGeo()) {//touches dateline; -180 == 180
BooleanQuery.Builder bq = new BooleanQuery.Builder();
bq.add(qMinX, BooleanClause.Occur.MUST);
bq.add(makeNumberTermQuery(field_maxX, 180.0), BooleanClause.Occur.MUST_NOT);
qMinX = bq.build();
}
Query qMaxX = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMinX(), false, false);
Query qMaxX = this.makeNumericRangeQuery(field_maxX, null, bbox.getMinX(), false, false);
if (bbox.getMaxX() == 180.0 && ctx.isGeo()) {//touches dateline; -180 == 180
BooleanQuery.Builder bq = new BooleanQuery.Builder();
bq.add(qMaxX, BooleanClause.Occur.MUST);
@ -368,8 +446,8 @@ public class BBoxStrategy extends SpatialStrategy {
// where: docMaxXLeft = 180.0, docMinXRight = -180.0
// (docMaxXLeft < queryExtent.getMinX()) equates to (180.0 < queryExtent.getMinX()) and is ignored
// (docMinXRight > queryExtent.getMaxX()) equates to (-180.0 > queryExtent.getMaxX()) and is ignored
Query qMinXLeft = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMaxX(), null, false, false);
Query qMaxXRight = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMinX(), false, false);
Query qMinXLeft = this.makeNumericRangeQuery(field_minX, bbox.getMaxX(), null, false, false);
Query qMaxXRight = this.makeNumericRangeQuery(field_maxX, null, bbox.getMinX(), false, false);
Query qLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qMinXLeft, qMaxXRight);
Query qXDL = this.makeXDL(true, qLeftRight);
@ -383,10 +461,10 @@ public class BBoxStrategy extends SpatialStrategy {
// the document must be disjoint to both the left and right query portions
// (docMinX > queryExtent.getMaxX()Left OR docMaxX < queryExtent.getMinX()) AND (docMinX > queryExtent.getMaxX() OR docMaxX < queryExtent.getMinX()Left)
// where: queryExtent.getMaxX()Left = 180.0, queryExtent.getMinX()Left = -180.0
Query qMinXLeft = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), 180.0, null, false, false);
Query qMaxXLeft = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMinX(), false, false);
Query qMinXRight = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMaxX(), null, false, false);
Query qMaxXRight = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, -180.0, false, false);
Query qMinXLeft = this.makeNumericRangeQuery(field_minX, 180.0, null, false, false);
Query qMaxXLeft = this.makeNumericRangeQuery(field_maxX, null, bbox.getMinX(), false, false);
Query qMinXRight = this.makeNumericRangeQuery(field_minX, bbox.getMaxX(), null, false, false);
Query qMaxXRight = this.makeNumericRangeQuery(field_maxX, null, -180.0, false, false);
Query qLeft = this.makeQuery(BooleanClause.Occur.SHOULD, qMinXLeft, qMaxXLeft);
Query qRight = this.makeQuery(BooleanClause.Occur.SHOULD, qMinXRight, qMaxXRight);
Query qLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qLeft, qRight);
@ -478,8 +556,8 @@ public class BBoxStrategy extends SpatialStrategy {
// Y conditions
// docMinY >= queryExtent.getMinY() AND docMaxY <= queryExtent.getMaxY()
Query qMinY = LegacyNumericRangeQuery.newDoubleRange(field_minY, getPrecisionStep(), bbox.getMinY(), null, true, false);
Query qMaxY = LegacyNumericRangeQuery.newDoubleRange(field_maxY, getPrecisionStep(), null, bbox.getMaxY(), false, true);
Query qMinY = this.makeNumericRangeQuery(field_minY, bbox.getMinY(), null, true, false);
Query qMaxY = this.makeNumericRangeQuery(field_maxY, null, bbox.getMaxY(), false, true);
Query yConditions = this.makeQuery(BooleanClause.Occur.MUST, qMinY, qMaxY);
// X conditions
@ -493,8 +571,8 @@ public class BBoxStrategy extends SpatialStrategy {
// queries that do not cross the date line
// docMinX >= queryExtent.getMinX() AND docMaxX <= queryExtent.getMaxX()
Query qMinX = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMinX(), null, true, false);
Query qMaxX = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMaxX(), false, true);
Query qMinX = this.makeNumericRangeQuery(field_minX, bbox.getMinX(), null, true, false);
Query qMaxX = this.makeNumericRangeQuery(field_maxX, null, bbox.getMaxX(), false, true);
Query qMinMax = this.makeQuery(BooleanClause.Occur.MUST, qMinX, qMaxX);
double edge = 0;//none, otherwise opposite dateline of query
@ -517,14 +595,14 @@ public class BBoxStrategy extends SpatialStrategy {
// the document should be within the left portion of the query
// docMinX >= queryExtent.getMinX() AND docMaxX <= 180.0
Query qMinXLeft = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMinX(), null, true, false);
Query qMaxXLeft = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, 180.0, false, true);
Query qMinXLeft = this.makeNumericRangeQuery(field_minX, bbox.getMinX(), null, true, false);
Query qMaxXLeft = this.makeNumericRangeQuery(field_maxX, null, 180.0, false, true);
Query qLeft = this.makeQuery(BooleanClause.Occur.MUST, qMinXLeft, qMaxXLeft);
// the document should be within the right portion of the query
// docMinX >= -180.0 AND docMaxX <= queryExtent.getMaxX()
Query qMinXRight = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), -180.0, null, true, false);
Query qMaxXRight = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMaxX(), false, true);
Query qMinXRight = this.makeNumericRangeQuery(field_minX, -180.0, null, true, false);
Query qMaxXRight = this.makeNumericRangeQuery(field_maxX, null, bbox.getMaxX(), false, true);
Query qRight = this.makeQuery(BooleanClause.Occur.MUST, qMinXRight, qMaxXRight);
// either left or right conditions should occur,
@ -537,8 +615,8 @@ public class BBoxStrategy extends SpatialStrategy {
// AND the right portion of the document must be within the right portion of the query
// docMinXLeft >= queryExtent.getMinX() AND docMaxXLeft <= 180.0
// AND docMinXRight >= -180.0 AND docMaxXRight <= queryExtent.getMaxX()
Query qXDLLeft = LegacyNumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMinX(), null, true, false);
Query qXDLRight = LegacyNumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMaxX(), false, true);
Query qXDLLeft = this.makeNumericRangeQuery(field_minX, bbox.getMinX(), null, true, false);
Query qXDLRight = this.makeNumericRangeQuery(field_maxX, null, bbox.getMaxX(), false, true);
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qXDLLeft, qXDLRight);
Query qXDL = this.makeXDL(true, qXDLLeftRight);
@ -581,9 +659,49 @@ public class BBoxStrategy extends SpatialStrategy {
}
private Query makeNumberTermQuery(String field, double number) {
BytesRefBuilder bytes = new BytesRefBuilder();
LegacyNumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(number), 0, bytes);
return new TermQuery(new Term(field, bytes.get()));
if (hasPointVals) {
return DoublePoint.newExactQuery(field, number);
} else if (legacyNumericFieldType != null) {
BytesRefBuilder bytes = new BytesRefBuilder();
LegacyNumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(number), 0, bytes);
return new TermQuery(new Term(field, bytes.get()));
}
throw new UnsupportedOperationException("An index is required for this operation.");
}
/**
* Returns a numeric range query based on FieldType
* {@link LegacyNumericRangeQuery} is used for indexes created using {@code FieldType.LegacyNumericType}
* {@link DoublePoint#newRangeQuery} is used for indexes created using {@link DoublePoint} fields
*
* @param fieldname field name. must not be <code>null</code>.
* @param min minimum value of the range.
* @param max maximum value of the range.
* @param minInclusive include the minimum value if <code>true</code>.
* @param maxInclusive include the maximum value if <code>true</code>
*/
private Query makeNumericRangeQuery(String fieldname, Double min, Double max, boolean minInclusive, boolean maxInclusive) {
if (hasPointVals) {
if (min == null) {
min = Double.NEGATIVE_INFINITY;
}
if (max == null) {
max = Double.POSITIVE_INFINITY;
}
if (minInclusive == false) {
min = Math.nextUp(min);
}
if (maxInclusive == false) {
max = Math.nextDown(max);
}
return DoublePoint.newRangeQuery(fieldname, min, max);
} else if (legacyNumericFieldType != null) {// todo remove legacy numeric support in 7.0
return LegacyNumericRangeQuery.newDoubleRange(fieldname, legacyNumericFieldType.numericPrecisionStep(), min, max, minInclusive, maxInclusive);
}
throw new UnsupportedOperationException("An index is required for this operation.");
}
}

View File

@ -16,14 +16,14 @@
*/
package org.apache.lucene.spatial.vector;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.document.LegacyDoubleField;
import org.apache.lucene.document.DoubleDocValuesField;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.LegacyDoubleField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.queries.function.FunctionRangeQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.BooleanClause;
@ -35,10 +35,15 @@ import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/**
* Simple {@link SpatialStrategy} which represents Points in two numeric {@link
* org.apache.lucene.document.LegacyDoubleField}s. The Strategy's best feature is decent distance sort.
* Simple {@link SpatialStrategy} which represents Points in two numeric fields.
* The Strategy's best feature is decent distance sort.
*
* <p>
* <b>Characteristics:</b>
@ -49,7 +54,7 @@ import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
* <li>{@link
* org.apache.lucene.spatial.query.SpatialOperation#Intersects} and {@link
* SpatialOperation#IsWithin} is supported.</li>
* <li>Uses the FieldCache for
* <li>Requires DocValues for
* {@link #makeDistanceValueSource(org.locationtech.spatial4j.shape.Point)} and for
* searching with a Circle.</li>
* </ul>
@ -57,8 +62,8 @@ import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
* <p>
* <b>Implementation:</b>
* <p>
* This is a simple Strategy. Search works with {@link org.apache.lucene.search.LegacyNumericRangeQuery}s on
* an x and y pair of fields. A Circle query does the same bbox query but adds a
* This is a simple Strategy. Search works with a pair of range queries on two {@link DoublePoint}s representing
* x &amp; y fields. A Circle query does the same bbox query but adds a
* ValueSource filter on
* {@link #makeDistanceValueSource(org.locationtech.spatial4j.shape.Point)}.
* <p>
@ -71,25 +76,104 @@ import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
*/
public class PointVectorStrategy extends SpatialStrategy {
// note: we use a FieldType to articulate the options we want on the field. We don't use it as-is with a Field, we
// create more than one Field.
/**
* pointValues, docValues, and nothing else.
*/
public static FieldType DEFAULT_FIELDTYPE;
@Deprecated
public static FieldType LEGACY_FIELDTYPE;
static {
// Default: pointValues + docValues
FieldType type = new FieldType();
type.setDimensions(1, Double.BYTES);//pointValues (assume Double)
type.setDocValuesType(DocValuesType.NUMERIC);//docValues
type.setStored(false);
type.freeze();
DEFAULT_FIELDTYPE = type;
// Legacy default: legacyNumerics
type = new FieldType();
type.setIndexOptions(IndexOptions.DOCS);
type.setNumericType(FieldType.LegacyNumericType.DOUBLE);
type.setNumericPrecisionStep(8);// same as solr default
type.setDocValuesType(DocValuesType.NONE);//no docValues!
type.setStored(false);
type.freeze();
LEGACY_FIELDTYPE = type;
}
public static final String SUFFIX_X = "__x";
public static final String SUFFIX_Y = "__y";
private final String fieldNameX;
private final String fieldNameY;
public int precisionStep = 8; // same as solr default
private final int fieldsLen;
private final boolean hasStored;
private final boolean hasDocVals;
private final boolean hasPointVals;
// equiv to "hasLegacyNumerics":
private final FieldType legacyNumericFieldType; // not stored; holds precision step.
public PointVectorStrategy(SpatialContext ctx, String fieldNamePrefix) {
/**
* Create a new {@link PointVectorStrategy} instance that uses {@link DoublePoint} and {@link DoublePoint#newRangeQuery}
*/
public static PointVectorStrategy newInstance(SpatialContext ctx, String fieldNamePrefix) {
return new PointVectorStrategy(ctx, fieldNamePrefix, DEFAULT_FIELDTYPE);
}
/**
* Create a new {@link PointVectorStrategy} instance that uses {@link LegacyDoubleField} for backwards compatibility.
* However, back-compat is limited; we don't support circle queries or {@link #makeDistanceValueSource(Point, double)}
* since that requires docValues (the legacy config didn't have that).
*
* @deprecated LegacyNumerics will be removed
*/
@Deprecated
public static PointVectorStrategy newLegacyInstance(SpatialContext ctx, String fieldNamePrefix) {
return new PointVectorStrategy(ctx, fieldNamePrefix, LEGACY_FIELDTYPE);
}
/**
* Create a new instance configured with the provided FieldType options. See {@link #DEFAULT_FIELDTYPE}.
* a field type is used to articulate the desired options (namely pointValues, docValues, stored). Legacy numerics
* is configurable this way too.
*/
public PointVectorStrategy(SpatialContext ctx, String fieldNamePrefix, FieldType fieldType) {
super(ctx, fieldNamePrefix);
this.fieldNameX = fieldNamePrefix+SUFFIX_X;
this.fieldNameY = fieldNamePrefix+SUFFIX_Y;
int numPairs = 0;
if ((this.hasStored = fieldType.stored())) {
numPairs++;
}
if ((this.hasDocVals = fieldType.docValuesType() != DocValuesType.NONE)) {
numPairs++;
}
if ((this.hasPointVals = fieldType.pointDimensionCount() > 0)) {
numPairs++;
}
if (fieldType.indexOptions() != IndexOptions.NONE && fieldType.numericType() != null) {
if (hasPointVals) {
throw new IllegalArgumentException("pointValues and LegacyNumericType are mutually exclusive");
}
if (fieldType.numericType() != FieldType.LegacyNumericType.DOUBLE) {
throw new IllegalArgumentException(getClass() + " does not support " + fieldType.numericType());
}
numPairs++;
legacyNumericFieldType = new FieldType(LegacyDoubleField.TYPE_NOT_STORED);
legacyNumericFieldType.setNumericPrecisionStep(fieldType.numericPrecisionStep());
legacyNumericFieldType.freeze();
} else {
legacyNumericFieldType = null;
}
this.fieldsLen = numPairs * 2;
}
public void setPrecisionStep( int p ) {
precisionStep = p;
if (precisionStep<=0 || precisionStep>=64)
precisionStep=Integer.MAX_VALUE;
}
String getFieldNameX() {
return fieldNameX;
@ -108,12 +192,26 @@ public class PointVectorStrategy extends SpatialStrategy {
/** @see #createIndexableFields(org.locationtech.spatial4j.shape.Shape) */
public Field[] createIndexableFields(Point point) {
FieldType doubleFieldType = new FieldType(LegacyDoubleField.TYPE_NOT_STORED);
doubleFieldType.setNumericPrecisionStep(precisionStep);
Field[] f = new Field[2];
f[0] = new LegacyDoubleField(fieldNameX, point.getX(), doubleFieldType);
f[1] = new LegacyDoubleField(fieldNameY, point.getY(), doubleFieldType);
return f;
Field[] fields = new Field[fieldsLen];
int idx = -1;
if (hasStored) {
fields[++idx] = new StoredField(fieldNameX, point.getX());
fields[++idx] = new StoredField(fieldNameY, point.getY());
}
if (hasDocVals) {
fields[++idx] = new DoubleDocValuesField(fieldNameX, point.getX());
fields[++idx] = new DoubleDocValuesField(fieldNameY, point.getY());
}
if (hasPointVals) {
fields[++idx] = new DoublePoint(fieldNameX, point.getX());
fields[++idx] = new DoublePoint(fieldNameY, point.getY());
}
if (legacyNumericFieldType != null) {
fields[++idx] = new LegacyDoubleField(fieldNameX, point.getX(), legacyNumericFieldType);
fields[++idx] = new LegacyDoubleField(fieldNameY, point.getY(), legacyNumericFieldType);
}
assert idx == fields.length - 1;
return fields;
}
@Override
@ -165,14 +263,27 @@ public class PointVectorStrategy extends SpatialStrategy {
return bq.build();
}
private LegacyNumericRangeQuery<Double> rangeQuery(String fieldName, Double min, Double max) {
return LegacyNumericRangeQuery.newDoubleRange(
fieldName,
precisionStep,
min,
max,
true,
true);//inclusive
}
/**
* Returns a numeric range query based on FieldType
* {@link LegacyNumericRangeQuery} is used for indexes created using {@code FieldType.LegacyNumericType}
* {@link DoublePoint#newRangeQuery} is used for indexes created using {@link DoublePoint} fields
*/
private Query rangeQuery(String fieldName, Double min, Double max) {
if (hasPointVals) {
if (min == null) {
min = Double.NEGATIVE_INFINITY;
}
if (max == null) {
max = Double.POSITIVE_INFINITY;
}
return DoublePoint.newRangeQuery(fieldName, min, max);
} else if (legacyNumericFieldType != null) {// todo remove legacy numeric support in 7.0
return LegacyNumericRangeQuery.newDoubleRange(fieldName, legacyNumericFieldType.numericPrecisionStep(), min, max, true, true);//inclusive
}
//TODO try doc-value range query?
throw new UnsupportedOperationException("An index is required for this operation.");
}
}

View File

@ -22,11 +22,6 @@ import java.util.Arrays;
import java.util.List;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
@ -37,6 +32,9 @@ import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
public class DistanceStrategyTest extends StrategyTestCase {
@ParametersFactory(argumentFormatting = "strategy=%s")
@ -59,10 +57,18 @@ public class DistanceStrategyTest extends StrategyTestCase {
strategy = new RecursivePrefixTreeStrategy(grid, "recursive_packedquad");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = new PointVectorStrategy(ctx, "pointvector");
strategy = PointVectorStrategy.newInstance(ctx, "pointvector");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = new BBoxStrategy(ctx, "bbox");
// Can't test this without un-inverting since PVS legacy config didn't have docValues.
// However, note that Solr's tests use UninvertingReader and thus test this.
// strategy = PointVectorStrategy.newLegacyInstance(ctx, "pointvector_legacy");
// ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = BBoxStrategy.newInstance(ctx, "bbox");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = BBoxStrategy.newLegacyInstance(ctx, "bbox_legacy");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = new SerializedDVStrategy(ctx, "serialized");
@ -76,22 +82,6 @@ public class DistanceStrategyTest extends StrategyTestCase {
this.strategy = strategy;
}
@Override
public void setUp() throws Exception {
super.setUp();
if (strategy instanceof BBoxStrategy && random().nextBoolean()) {//disable indexing sometimes
BBoxStrategy bboxStrategy = (BBoxStrategy)strategy;
final FieldType fieldType = new FieldType(bboxStrategy.getFieldType());
fieldType.setIndexOptions(IndexOptions.NONE);
bboxStrategy.setFieldType(fieldType);
}
}
@Override
protected boolean needsDocValues() {
return true;
}
@Test
public void testDistanceOrder() throws IOException {
adoc("100", ctx.makePoint(2, 1));
@ -108,9 +98,9 @@ public class DistanceStrategyTest extends StrategyTestCase {
@Test
public void testRecipScore() throws IOException {
Point p100 = ctx.makePoint(2, 1);
Point p100 = ctx.makePoint(2.02, 0.98);
adoc("100", p100);
Point p101 = ctx.makePoint(-1, 4);
Point p101 = ctx.makePoint(-1.001, 4.001);
adoc("101", p101);
adoc("103", (Shape)null);//test score for nothing
adoc("999", ctx.makePoint(2, 1));//test deleted

View File

@ -22,10 +22,6 @@ import java.util.List;
import java.util.Set;
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.Shape;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
@ -36,6 +32,10 @@ import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
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.Shape;
/**
* Based off of Solr 3's SpatialFilterTest.
@ -62,7 +62,10 @@ public class PortedSolr3Test extends StrategyTestCase {
strategy = new TermQueryPrefixTreeStrategy(grid, "termquery_geohash");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = new PointVectorStrategy(ctx, "pointvector");
strategy = PointVectorStrategy.newInstance(ctx, "pointvector");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
strategy = PointVectorStrategy.newInstance(ctx, "pointvector_legacy");
ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});
return ctorArgs;

View File

@ -19,8 +19,6 @@ package org.apache.lucene.spatial;
import java.util.ArrayList;
import java.util.Collection;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
@ -34,6 +32,8 @@ import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
import org.apache.lucene.util.LuceneTestCase;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Shape;
public class QueryEqualsHashCodeTest extends LuceneTestCase {
@ -57,8 +57,10 @@ public class QueryEqualsHashCodeTest extends LuceneTestCase {
RecursivePrefixTreeStrategy recursive_geohash = new RecursivePrefixTreeStrategy(gridGeohash, "recursive_geohash");
strategies.add(recursive_geohash);
strategies.add(new TermQueryPrefixTreeStrategy(gridQuad, "termquery_quad"));
strategies.add(new PointVectorStrategy(ctx, "pointvector"));
strategies.add(new BBoxStrategy(ctx, "bbox"));
strategies.add(PointVectorStrategy.newInstance(ctx, "pointvector"));
strategies.add(PointVectorStrategy.newLegacyInstance(ctx, "pointvector_legacy"));
strategies.add(BBoxStrategy.newInstance(ctx, "bbox"));
strategies.add(BBoxStrategy.newLegacyInstance(ctx, "bbox_legacy"));
final SerializedDVStrategy serialized = new SerializedDVStrategy(ctx, "serialized");
strategies.add(serialized);
strategies.add(new CompositeSpatialStrategy("composite", recursive_geohash, serialized));

View File

@ -18,34 +18,26 @@ package org.apache.lucene.spatial;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Logger;
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.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.uninverting.UninvertingReader;
import org.apache.lucene.uninverting.UninvertingReader.Type;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.LuceneTestCase.SuppressSysoutChecks;
import org.apache.lucene.util.TestUtil;
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 static com.carrotsearch.randomizedtesting.RandomizedTest.randomDouble;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomGaussian;
@ -66,36 +58,16 @@ public abstract class SpatialTestCase extends LuceneTestCase {
protected SpatialContext ctx;//subclass must initialize
protected Map<String,Type> uninvertMap = new HashMap<>();
@Override
public void setUp() throws Exception {
super.setUp();
// TODO: change this module to index docvalues instead of uninverting
uninvertMap.clear();
uninvertMap.put("pointvector__x", Type.LEGACY_DOUBLE);
uninvertMap.put("pointvector__y", Type.LEGACY_DOUBLE);
directory = newDirectory();
final Random random = random();
analyzer = new MockAnalyzer(random);
indexWriter = new RandomIndexWriter(random,directory, newIWConfig(random, analyzer));
indexReader = UninvertingReader.wrap(indexWriter.getReader(), uninvertMap);
analyzer = new MockAnalyzer(random());
indexWriter = new RandomIndexWriter(random(), directory, LuceneTestCase.newIndexWriterConfig(random(), analyzer));
indexReader = indexWriter.getReader();
indexSearcher = newSearcher(indexReader);
}
protected IndexWriterConfig newIWConfig(Random random, Analyzer analyzer) {
final IndexWriterConfig indexWriterConfig = LuceneTestCase.newIndexWriterConfig(random, analyzer);
//TODO can we randomly choose a doc-values supported format?
if (needsDocValues())
indexWriterConfig.setCodec( TestUtil.getDefaultCodec());
return indexWriterConfig;
}
protected boolean needsDocValues() {
return false;
}
@Override
public void tearDown() throws Exception {
IOUtils.close(indexWriter, indexReader, analyzer, directory);

View File

@ -19,12 +19,6 @@ package org.apache.lucene.spatial.bbox;
import java.io.IOException;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
@ -36,14 +30,15 @@ import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
import org.junit.Ignore;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
@Override
protected boolean needsDocValues() {
return true;
}
@Override
protected Shape randomIndexedShape() {
Rectangle world = ctx.getWorldBounds();
@ -97,13 +92,17 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
factory.worldBounds = new RectangleImpl(-300, 300, -100, 100, null);
this.ctx = factory.newSpatialContext();
}
this.strategy = new BBoxStrategy(ctx, "bbox");
// randomly test legacy (numeric) and point based bbox strategy
if (random().nextBoolean()) {
this.strategy = BBoxStrategy.newInstance(ctx, "bbox");
} else {
this.strategy = BBoxStrategy.newLegacyInstance(ctx, "bbox");
}
//test we can disable docValues for predicate tests
if (random().nextBoolean()) {
BBoxStrategy bboxStrategy = (BBoxStrategy) strategy;
FieldType fieldType = new FieldType(bboxStrategy.getFieldType());
FieldType fieldType = new FieldType(((BBoxStrategy)strategy).getFieldType());
fieldType.setDocValuesType(DocValuesType.NONE);
bboxStrategy.setFieldType(fieldType);
strategy = new BBoxStrategy(ctx, strategy.getFieldName(), fieldType);
}
for (SpatialOperation operation : SpatialOperation.values()) {
if (operation == SpatialOperation.Overlaps)
@ -189,7 +188,11 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
private void setupGeo() {
this.ctx = SpatialContext.GEO;
this.strategy = new BBoxStrategy(ctx, "bbox");
if (random().nextBoolean()) {
this.strategy = BBoxStrategy.newInstance(ctx, "bbox");
} else {
this.strategy = BBoxStrategy.newLegacyInstance(ctx, "bbox");
}
}
// OLD STATIC TESTS (worthless?)
@ -225,8 +228,29 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
return shape.getBoundingBox();
}
private BBoxStrategy setupNeedsDocValuesOnly() throws IOException {
this.ctx = SpatialContext.GEO;
FieldType fieldType;
// random legacy or not legacy
String FIELD_PREFIX = "bbox";
if (random().nextBoolean()) {
fieldType = new FieldType(BBoxStrategy.DEFAULT_FIELDTYPE);
if (random().nextBoolean()) {
fieldType.setDimensions(0, 0);
}
} else {
fieldType = new FieldType(BBoxStrategy.LEGACY_FIELDTYPE);
if (random().nextBoolean()) {
fieldType.setIndexOptions(IndexOptions.NONE);
}
}
strategy = new BBoxStrategy(ctx, FIELD_PREFIX, fieldType);
return (BBoxStrategy)strategy;
}
public void testOverlapRatio() throws IOException {
setupGeo();
setupNeedsDocValuesOnly();
//Simply assert null shape results in 0
adoc("999", (Shape) null);
@ -279,14 +303,7 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
}
public void testAreaValueSource() throws IOException {
setupGeo();
//test we can disable indexed for this test
BBoxStrategy bboxStrategy = (BBoxStrategy) strategy;
if (random().nextBoolean()) {
FieldType fieldType = new FieldType(bboxStrategy.getFieldType());
fieldType.setIndexOptions(IndexOptions.NONE);
bboxStrategy.setFieldType(fieldType);
}
BBoxStrategy bboxStrategy = setupNeedsDocValuesOnly();
adoc("100", ctx.makeRectangle(0, 20, 40, 80));
adoc("999", (Shape) null);
@ -298,4 +315,5 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, true, 2.0),
new float[]{783.86f, 0f}, 0.01f); // testing with a different multiplier
}
}

View File

@ -19,12 +19,6 @@ package org.apache.lucene.spatial.composite;
import java.io.IOException;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
@ -33,6 +27,12 @@ import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomDouble;
@ -101,11 +101,6 @@ public class CompositeStrategyTest extends RandomSpatialOpStrategyTestCase {
}
}
@Override
protected boolean needsDocValues() {
return true;//due to SerializedDVStrategy
}
@Override
protected Shape randomIndexedShape() {
return randomShape();

View File

@ -18,11 +18,11 @@ package org.apache.lucene.spatial.serialized;
import java.io.IOException;
import org.locationtech.spatial4j.context.SpatialContext;
import org.apache.lucene.spatial.SpatialMatchConcern;
import org.apache.lucene.spatial.StrategyTestCase;
import org.junit.Before;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
public class SerializedStrategyTest extends StrategyTestCase {
@ -34,11 +34,6 @@ public class SerializedStrategyTest extends StrategyTestCase {
this.strategy = new SerializedDVStrategy(ctx, "serialized");
}
@Override
protected boolean needsDocValues() {
return (strategy instanceof SerializedDVStrategy);
}
@Test
public void testBasicOperaions() throws IOException {
getAddAndVerifyIndexedDocuments(DATA_SIMPLE_BBOX);

View File

@ -21,10 +21,6 @@ import java.util.ArrayList;
import java.util.List;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
@ -33,13 +29,17 @@ import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoStandardCircle;
import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.GeoStandardCircle;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
@ -63,11 +63,6 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
return rpt;
}
@Override
protected boolean needsDocValues() {
return true;//due to SerializedDVStrategy
}
private void setupStrategy() {
//setup
setupGeohashGrid();

View File

@ -16,9 +16,12 @@
*/
package org.apache.lucene.spatial.vector;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import java.io.IOException;
import java.text.ParseException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SpatialMatchConcern;
import org.apache.lucene.spatial.StrategyTestCase;
@ -26,8 +29,9 @@ import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
public class TestPointVectorStrategy extends StrategyTestCase {
@ -36,11 +40,11 @@ public class TestPointVectorStrategy extends StrategyTestCase {
public void setUp() throws Exception {
super.setUp();
this.ctx = SpatialContext.GEO;
this.strategy = new PointVectorStrategy(ctx, getClass().getSimpleName());
}
@Test
public void testCircleShapeSupport() {
this.strategy = PointVectorStrategy.newInstance(ctx, getClass().getSimpleName());
Circle circle = ctx.makeCircle(ctx.makePoint(0, 0), 10);
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, circle);
Query query = this.strategy.makeQuery(args);
@ -50,6 +54,7 @@ public class TestPointVectorStrategy extends StrategyTestCase {
@Test(expected = UnsupportedOperationException.class)
public void testInvalidQueryShape() {
this.strategy = PointVectorStrategy.newInstance(ctx, getClass().getSimpleName());
Point point = ctx.makePoint(0, 0);
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, point);
this.strategy.makeQuery(args);
@ -57,7 +62,45 @@ public class TestPointVectorStrategy extends StrategyTestCase {
@Test
public void testCitiesIntersectsBBox() throws IOException {
// note: does not require docValues
if (random().nextBoolean()) {
this.strategy = PointVectorStrategy.newInstance(ctx, getClass().getSimpleName());
} else {
// switch to legacy instance sometimes, which has no docValues
this.strategy = PointVectorStrategy.newLegacyInstance(ctx, getClass().getSimpleName());
}
getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_Intersects_BBox);
}
@Test
public void testFieldOptions() throws IOException, ParseException {
// It's not stored; test it isn't.
this.strategy = PointVectorStrategy.newInstance(ctx, getClass().getSimpleName());
adoc("99", "POINT(-5.0 8.2)");
commit();
SearchResults results = executeQuery(new MatchAllDocsQuery(), 1);
Document document = results.results.get(0).document;
assertNull("not stored", document.getField(strategy.getFieldName() + PointVectorStrategy.SUFFIX_X));
assertNull("not stored", document.getField(strategy.getFieldName() + PointVectorStrategy.SUFFIX_Y));
deleteAll();
// Now we mark it stored. We also disable pointvalues...
FieldType fieldType = new FieldType(PointVectorStrategy.DEFAULT_FIELDTYPE);
fieldType.setStored(true);
fieldType.setDimensions(0, 0);//disable point values
this.strategy = new PointVectorStrategy(ctx, getClass().getSimpleName(), fieldType);
adoc("99", "POINT(-5.0 8.2)");
commit();
results = executeQuery(new MatchAllDocsQuery(), 1);
document = results.results.get(0).document;
assertEquals("stored", -5.0, document.getField(strategy.getFieldName() + PointVectorStrategy.SUFFIX_X).numericValue());
assertEquals("stored", 8.2, document.getField(strategy.getFieldName() + PointVectorStrategy.SUFFIX_Y).numericValue());
// Test a query fails without point values
expectThrows(UnsupportedOperationException.class, () -> {
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, ctx.makeRectangle(-10.0, 10.0, -5.0, 5.0));
this.strategy.makeQuery(args);
});
}
}

View File

@ -22,7 +22,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.locationtech.spatial4j.shape.Rectangle;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource;
@ -31,6 +30,7 @@ import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
import org.apache.solr.common.SolrException;
import org.apache.solr.search.QParser;
import org.locationtech.spatial4j.shape.Rectangle;
public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements SchemaAware {
private static final String PARAM_QUERY_TARGET_PROPORTION = "queryTargetProportion";
@ -133,7 +133,6 @@ public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements
registerSubFields(schema, fieldName, numberType, booleanType);
}
BBoxStrategy strategy = new BBoxStrategy(ctx, fieldName);
//Solr's FieldType ought to expose Lucene FieldType. Instead as a hack we create a Field with a dummy value.
final SchemaField solrNumField = new SchemaField("_", numberType);//dummy temp
org.apache.lucene.document.FieldType luceneType =
@ -145,8 +144,7 @@ public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements
luceneType = new org.apache.lucene.document.FieldType(luceneType);
luceneType.setDocValuesType(DocValuesType.NUMERIC);
}
strategy.setFieldType(luceneType);
return strategy;
return new BBoxStrategy(ctx, fieldName, luceneType);
}
@Override

View File

@ -16,12 +16,12 @@
*/
package org.apache.solr.schema;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
/**
* @see PointVectorStrategy
* @lucene.experimental
@ -77,11 +77,24 @@ public class SpatialPointVectorFieldType extends AbstractSpatialFieldType<PointV
}
}
@Override
public org.apache.lucene.document.FieldType.LegacyNumericType getNumericType() {
return org.apache.lucene.document.FieldType.LegacyNumericType.DOUBLE;
}
@Override
protected PointVectorStrategy newSpatialStrategy(String fieldName) {
PointVectorStrategy strategy = new PointVectorStrategy(ctx, fieldName);
strategy.setPrecisionStep(precisionStep);
return strategy;
// TODO update to how BBoxField does things
if (this.getNumericType() != null) {
// create strategy based on legacy numerics
// todo remove in 7.0
org.apache.lucene.document.FieldType fieldType =
new org.apache.lucene.document.FieldType(PointVectorStrategy.LEGACY_FIELDTYPE);
fieldType.setNumericPrecisionStep(precisionStep);
return new PointVectorStrategy(ctx, fieldName, fieldType);
} else {
return PointVectorStrategy.newInstance(ctx, fieldName);
}
}
}