mirror of https://github.com/apache/lucene.git
LUCENE-4175: adding bbox strategy
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1354841 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
8127865e2d
commit
26d3f1f813
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* 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.bbox;
|
||||||
|
|
||||||
|
import org.apache.lucene.search.Explanation;
|
||||||
|
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The algorithm is implemented as envelope on envelope overlays rather than
|
||||||
|
* complex polygon on complex polygon overlays.
|
||||||
|
* <p/>
|
||||||
|
* <p/>
|
||||||
|
* Spatial relevance scoring algorithm:
|
||||||
|
* <p/>
|
||||||
|
* <br/> queryArea = the area of the input query envelope
|
||||||
|
* <br/> targetArea = the area of the target envelope (per Lucene document)
|
||||||
|
* <br/> intersectionArea = the area of the intersection for the query/target envelopes
|
||||||
|
* <br/> queryPower = the weighting power associated with the query envelope (default = 1.0)
|
||||||
|
* <br/> targetPower = the weighting power associated with the target envelope (default = 1.0)
|
||||||
|
* <p/>
|
||||||
|
* <br/> queryRatio = intersectionArea / queryArea;
|
||||||
|
* <br/> targetRatio = intersectionArea / targetArea;
|
||||||
|
* <br/> queryFactor = Math.pow(queryRatio,queryPower);
|
||||||
|
* <br/> targetFactor = Math.pow(targetRatio,targetPower);
|
||||||
|
* <br/> score = queryFactor * targetFactor;
|
||||||
|
* <p/>
|
||||||
|
* original:
|
||||||
|
* http://geoportal.svn.sourceforge.net/svnroot/geoportal/Geoportal/trunk/src/com/esri/gpt/catalog/lucene/SpatialRankingValueSource.java
|
||||||
|
*/
|
||||||
|
public class AreaSimilarity implements BBoxSimilarity {
|
||||||
|
/**
|
||||||
|
* Properties associated with the query envelope
|
||||||
|
*/
|
||||||
|
private final Rectangle queryExtent;
|
||||||
|
private final double queryArea;
|
||||||
|
|
||||||
|
private final double targetPower;
|
||||||
|
private final double queryPower;
|
||||||
|
|
||||||
|
public AreaSimilarity(Rectangle queryExtent, double queryPower, double targetPower) {
|
||||||
|
this.queryExtent = queryExtent;
|
||||||
|
this.queryArea = queryExtent.getArea();
|
||||||
|
|
||||||
|
this.queryPower = queryPower;
|
||||||
|
this.targetPower = targetPower;
|
||||||
|
|
||||||
|
// if (this.qryMinX > queryExtent.getMaxX()) {
|
||||||
|
// this.qryCrossedDateline = true;
|
||||||
|
// this.qryArea = Math.abs(qryMaxX + 360.0 - qryMinX) * Math.abs(qryMaxY - qryMinY);
|
||||||
|
// } else {
|
||||||
|
// this.qryArea = Math.abs(qryMaxX - qryMinX) * Math.abs(qryMaxY - qryMinY);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public AreaSimilarity(Rectangle queryExtent) {
|
||||||
|
this(queryExtent, 2.0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getDelimiterQueryParameters() {
|
||||||
|
return queryExtent.toString() + ";" + queryPower + ";" + targetPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double score(Rectangle target, Explanation exp) {
|
||||||
|
if (target == null || queryArea <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
double targetArea = target.getArea();
|
||||||
|
if (targetArea <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
double score = 0;
|
||||||
|
|
||||||
|
double top = Math.min(queryExtent.getMaxY(), target.getMaxY());
|
||||||
|
double bottom = Math.max(queryExtent.getMinY(), target.getMinY());
|
||||||
|
double height = top - bottom;
|
||||||
|
double width = 0;
|
||||||
|
|
||||||
|
// queries that cross the date line
|
||||||
|
if (queryExtent.getCrossesDateLine()) {
|
||||||
|
// documents that cross the date line
|
||||||
|
if (target.getCrossesDateLine()) {
|
||||||
|
double left = Math.max(queryExtent.getMinX(), target.getMinX());
|
||||||
|
double right = Math.min(queryExtent.getMaxX(), target.getMaxX());
|
||||||
|
width = right + 360.0 - left;
|
||||||
|
} else {
|
||||||
|
double qryWestLeft = Math.max(queryExtent.getMinX(), target.getMaxX());
|
||||||
|
double qryWestRight = Math.min(target.getMaxX(), 180.0);
|
||||||
|
double qryWestWidth = qryWestRight - qryWestLeft;
|
||||||
|
if (qryWestWidth > 0) {
|
||||||
|
width = qryWestWidth;
|
||||||
|
} else {
|
||||||
|
double qryEastLeft = Math.max(target.getMaxX(), -180.0);
|
||||||
|
double qryEastRight = Math.min(queryExtent.getMaxX(), target.getMaxX());
|
||||||
|
double qryEastWidth = qryEastRight - qryEastLeft;
|
||||||
|
if (qryEastWidth > 0) {
|
||||||
|
width = qryEastWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // queries that do not cross the date line
|
||||||
|
|
||||||
|
if (target.getCrossesDateLine()) {
|
||||||
|
double tgtWestLeft = Math.max(queryExtent.getMinX(), target.getMinX());
|
||||||
|
double tgtWestRight = Math.min(queryExtent.getMaxX(), 180.0);
|
||||||
|
double tgtWestWidth = tgtWestRight - tgtWestLeft;
|
||||||
|
if (tgtWestWidth > 0) {
|
||||||
|
width = tgtWestWidth;
|
||||||
|
} else {
|
||||||
|
double tgtEastLeft = Math.max(queryExtent.getMinX(), -180.0);
|
||||||
|
double tgtEastRight = Math.min(queryExtent.getMaxX(), target.getMaxX());
|
||||||
|
double tgtEastWidth = tgtEastRight - tgtEastLeft;
|
||||||
|
if (tgtEastWidth > 0) {
|
||||||
|
width = tgtEastWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
double left = Math.max(queryExtent.getMinX(), target.getMinX());
|
||||||
|
double right = Math.min(queryExtent.getMaxX(), target.getMaxX());
|
||||||
|
width = right - left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// calculate the score
|
||||||
|
if ((width > 0) && (height > 0)) {
|
||||||
|
double intersectionArea = width * height;
|
||||||
|
double queryRatio = intersectionArea / queryArea;
|
||||||
|
double targetRatio = intersectionArea / targetArea;
|
||||||
|
double queryFactor = Math.pow(queryRatio, queryPower);
|
||||||
|
double targetFactor = Math.pow(targetRatio, targetPower);
|
||||||
|
score = queryFactor * targetFactor * 10000.0;
|
||||||
|
|
||||||
|
if (exp!=null) {
|
||||||
|
// StringBuilder sb = new StringBuilder();
|
||||||
|
// sb.append("\nscore=").append(score);
|
||||||
|
// sb.append("\n query=").append();
|
||||||
|
// sb.append("\n target=").append(target.toString());
|
||||||
|
// sb.append("\n intersectionArea=").append(intersectionArea);
|
||||||
|
//
|
||||||
|
// sb.append(" queryArea=").append(queryArea).append(" targetArea=").append(targetArea);
|
||||||
|
// sb.append("\n queryRatio=").append(queryRatio).append(" targetRatio=").append(targetRatio);
|
||||||
|
// sb.append("\n queryFactor=").append(queryFactor).append(" targetFactor=").append(targetFactor);
|
||||||
|
// sb.append(" (queryPower=").append(queryPower).append(" targetPower=").append(targetPower).append(")");
|
||||||
|
|
||||||
|
exp.setValue((float)score);
|
||||||
|
exp.setDescription(this.getClass().getSimpleName());
|
||||||
|
|
||||||
|
Explanation e = null;
|
||||||
|
|
||||||
|
exp.addDetail( e = new Explanation((float)intersectionArea, "IntersectionArea") );
|
||||||
|
e.addDetail(new Explanation((float)width, "width; Query: "+queryExtent.toString()));
|
||||||
|
e.addDetail(new Explanation((float)height, "height; Target: "+target.toString()));
|
||||||
|
|
||||||
|
exp.addDetail( e = new Explanation((float)queryFactor, "Query") );
|
||||||
|
e.addDetail(new Explanation((float)queryArea, "area"));
|
||||||
|
e.addDetail(new Explanation((float)queryRatio, "ratio"));
|
||||||
|
e.addDetail(new Explanation((float)queryPower, "power"));
|
||||||
|
|
||||||
|
exp.addDetail( e = new Explanation((float)targetFactor, "Target") );
|
||||||
|
e.addDetail(new Explanation((float)targetArea, "area"));
|
||||||
|
e.addDetail(new Explanation((float)targetRatio, "ratio"));
|
||||||
|
e.addDetail(new Explanation((float)targetPower, "power"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(exp !=null) {
|
||||||
|
exp.setValue(0);
|
||||||
|
exp.setDescription("Shape does not intersect");
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this ValueSource is equal to another.
|
||||||
|
*
|
||||||
|
* @param o the ValueSource to compare
|
||||||
|
* @return <code>true</code> if the two objects are based upon the same query envelope
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o.getClass() != AreaSimilarity.class)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AreaSimilarity other = (AreaSimilarity) o;
|
||||||
|
return getDelimiterQueryParameters().equals(other.getDelimiterQueryParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ValueSource hash code.
|
||||||
|
*
|
||||||
|
* @return the hash code
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getDelimiterQueryParameters().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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.bbox;
|
||||||
|
|
||||||
|
import org.apache.lucene.spatial.SpatialFieldInfo;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
public class BBoxFieldInfo implements SpatialFieldInfo {
|
||||||
|
|
||||||
|
public static final String SUFFIX_MINX = "__minX";
|
||||||
|
public static final String SUFFIX_MAXX = "__maxX";
|
||||||
|
public static final String SUFFIX_MINY = "__minY";
|
||||||
|
public static final String SUFFIX_MAXY = "__maxY";
|
||||||
|
public static final String SUFFIX_XDL = "__xdl";
|
||||||
|
|
||||||
|
public String bbox = "bbox";
|
||||||
|
public String minX = "bbox.minx";
|
||||||
|
public String minY = "bbox.miny";
|
||||||
|
public String maxX = "bbox.maxx";
|
||||||
|
public String maxY = "bbox.maxy";
|
||||||
|
public String xdl = "bbox.xdl"; // crosses dateline
|
||||||
|
|
||||||
|
public BBoxFieldInfo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public BBoxFieldInfo( String p ) {
|
||||||
|
this.setFieldsPrefix( p );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFieldsPrefix(String prefix) {
|
||||||
|
bbox = prefix;
|
||||||
|
minX = prefix + SUFFIX_MINX;
|
||||||
|
maxX = prefix + SUFFIX_MAXX;
|
||||||
|
minY = prefix + SUFFIX_MINY;
|
||||||
|
maxY = prefix + SUFFIX_MAXY;
|
||||||
|
xdl = prefix + SUFFIX_XDL;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.bbox;
|
||||||
|
|
||||||
|
import org.apache.lucene.search.Explanation;
|
||||||
|
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public interface BBoxSimilarity {
|
||||||
|
|
||||||
|
public double score(Rectangle extent, Explanation exp);
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* 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.bbox;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.lucene.index.AtomicReader;
|
||||||
|
import org.apache.lucene.index.AtomicReaderContext;
|
||||||
|
import org.apache.lucene.queries.function.FunctionValues;
|
||||||
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
|
import org.apache.lucene.search.Explanation;
|
||||||
|
import org.apache.lucene.search.FieldCache;
|
||||||
|
import org.apache.lucene.util.Bits;
|
||||||
|
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
import com.spatial4j.core.shape.simple.RectangleImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the Lucene ValueSource model to support spatial relevance ranking.
|
||||||
|
*/
|
||||||
|
public class BBoxSimilarityValueSource extends ValueSource {
|
||||||
|
|
||||||
|
private final BBoxFieldInfo field;
|
||||||
|
private final BBoxSimilarity similarity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param queryEnvelope the query envelope
|
||||||
|
* @param queryPower the query power (scoring algorithm)
|
||||||
|
* @param targetPower the target power (scoring algorithm)
|
||||||
|
*/
|
||||||
|
public BBoxSimilarityValueSource(BBoxSimilarity similarity, BBoxFieldInfo field) {
|
||||||
|
this.similarity = similarity;
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ValueSource description.
|
||||||
|
*
|
||||||
|
* @return the description
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "BBoxSimilarityValueSource(" + similarity + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the DocValues used by the function query.
|
||||||
|
*
|
||||||
|
* @param reader the index reader
|
||||||
|
* @return the values
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||||
|
AtomicReader reader = readerContext.reader();
|
||||||
|
final double[] minX = FieldCache.DEFAULT.getDoubles(reader, field.minX, true);
|
||||||
|
final double[] minY = FieldCache.DEFAULT.getDoubles(reader, field.minY, true);
|
||||||
|
final double[] maxX = FieldCache.DEFAULT.getDoubles(reader, field.maxX, true);
|
||||||
|
final double[] maxY = FieldCache.DEFAULT.getDoubles(reader, field.maxY, true);
|
||||||
|
|
||||||
|
final Bits validMinX = FieldCache.DEFAULT.getDocsWithField(reader, field.minX);
|
||||||
|
final Bits validMaxX = FieldCache.DEFAULT.getDocsWithField(reader, field.maxX);
|
||||||
|
|
||||||
|
return new FunctionValues() {
|
||||||
|
@Override
|
||||||
|
public float floatVal(int doc) {
|
||||||
|
// make sure it has minX and area
|
||||||
|
if (validMinX.get(doc) && validMaxX.get(doc)) {
|
||||||
|
Rectangle rect = new RectangleImpl(
|
||||||
|
minX[doc], maxX[doc],
|
||||||
|
minY[doc], maxY[doc]);
|
||||||
|
return (float) similarity.score(rect, null);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Explanation explain(int doc) {
|
||||||
|
// make sure it has minX and area
|
||||||
|
if (validMinX.get(doc) && validMaxX.get(doc)) {
|
||||||
|
Rectangle rect = new RectangleImpl(
|
||||||
|
minX[doc], maxX[doc],
|
||||||
|
minY[doc], maxY[doc]);
|
||||||
|
Explanation exp = new Explanation();
|
||||||
|
similarity.score(rect, exp);
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
return new Explanation(0, "No BBox");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(int doc) {
|
||||||
|
return description() + "=" + floatVal(doc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this ValueSource is equal to another.
|
||||||
|
*
|
||||||
|
* @param o the ValueSource to compare
|
||||||
|
* @return <code>true</code> if the two objects are based upon the same query envelope
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o.getClass() != BBoxSimilarityValueSource.class) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BBoxSimilarityValueSource other = (BBoxSimilarityValueSource) o;
|
||||||
|
return similarity.equals(other.similarity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return BBoxSimilarityValueSource.class.hashCode() + similarity.hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,472 @@
|
||||||
|
/*
|
||||||
|
* 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.bbox;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.apache.lucene.document.Field;
|
||||||
|
import org.apache.lucene.document.FieldType;
|
||||||
|
import org.apache.lucene.index.FieldInfo.IndexOptions;
|
||||||
|
import org.apache.lucene.index.IndexableField;
|
||||||
|
import org.apache.lucene.index.Term;
|
||||||
|
import org.apache.lucene.queries.function.FunctionQuery;
|
||||||
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
|
import org.apache.lucene.search.BooleanClause;
|
||||||
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
|
import org.apache.lucene.search.Filter;
|
||||||
|
import org.apache.lucene.search.NumericRangeQuery;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.search.QueryWrapperFilter;
|
||||||
|
import org.apache.lucene.search.TermQuery;
|
||||||
|
import org.apache.lucene.spatial.SpatialStrategy;
|
||||||
|
import org.apache.lucene.spatial.util.NumericFieldInfo;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.*;
|
||||||
|
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
|
||||||
|
import com.spatial4j.core.query.*;
|
||||||
|
import com.spatial4j.core.shape.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* original:
|
||||||
|
* http://geoportal.svn.sourceforge.net/svnroot/geoportal/Geoportal/trunk/src/com/esri/gpt/catalog/lucene/SpatialClauseAdapter.java
|
||||||
|
*/
|
||||||
|
public class BBoxStrategy extends SpatialStrategy<BBoxFieldInfo> {
|
||||||
|
public double queryPower = 1.0;
|
||||||
|
public double targetPower = 1.0f;
|
||||||
|
|
||||||
|
public NumericFieldInfo finfo = null;
|
||||||
|
|
||||||
|
public BBoxStrategy(SpatialContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------
|
||||||
|
// Indexing
|
||||||
|
//---------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IndexableField[] createFields(BBoxFieldInfo fieldInfo,
|
||||||
|
Shape shape, boolean index, boolean store) {
|
||||||
|
|
||||||
|
Rectangle bbox = shape.getBoundingBox();
|
||||||
|
IndexableField[] fields = new IndexableField[store?6:5];
|
||||||
|
fields[0] = finfo.createDouble(fieldInfo.minX, bbox.getMinX());
|
||||||
|
fields[1] = finfo.createDouble(fieldInfo.maxX, bbox.getMaxX());
|
||||||
|
fields[2] = finfo.createDouble(fieldInfo.minY, bbox.getMinY());
|
||||||
|
fields[3] = finfo.createDouble(fieldInfo.maxY, bbox.getMaxY());
|
||||||
|
|
||||||
|
FieldType ft = new FieldType();
|
||||||
|
ft.setIndexed(index);
|
||||||
|
ft.setStored(store);
|
||||||
|
ft.setTokenized(false);
|
||||||
|
ft.setOmitNorms(true);
|
||||||
|
ft.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||||
|
ft.freeze();
|
||||||
|
|
||||||
|
Field xdl = new Field( fieldInfo.xdl, bbox.getCrossesDateLine()?"T":"F", ft );
|
||||||
|
fields[4] = xdl;
|
||||||
|
if( store ) {
|
||||||
|
FieldType ff = new FieldType();
|
||||||
|
ff.setIndexed(false);
|
||||||
|
ff.setStored(true);
|
||||||
|
ff.setOmitNorms(true);
|
||||||
|
ff.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||||
|
ff.freeze();
|
||||||
|
|
||||||
|
NumberFormat nf = NumberFormat.getInstance( Locale.US );
|
||||||
|
nf.setMaximumFractionDigits( 5 );
|
||||||
|
nf.setMinimumFractionDigits( 5 );
|
||||||
|
nf.setGroupingUsed(false);
|
||||||
|
String ext =
|
||||||
|
nf.format( bbox.getMinX() ) + ' ' +
|
||||||
|
nf.format( bbox.getMinY() ) + ' ' +
|
||||||
|
nf.format( bbox.getMaxX() ) + ' ' +
|
||||||
|
nf.format( bbox.getMaxY() ) + ' ';
|
||||||
|
fields[5] = new Field( fieldInfo.bbox, ext, ff );
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IndexableField createField(BBoxFieldInfo fieldInfo, Shape shape,
|
||||||
|
boolean index, boolean store) {
|
||||||
|
throw new UnsupportedOperationException("BBOX is poly field");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPolyField() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------
|
||||||
|
// Query Builder
|
||||||
|
//---------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueSource makeValueSource(SpatialArgs args, BBoxFieldInfo fields) {
|
||||||
|
return new BBoxSimilarityValueSource(
|
||||||
|
new AreaSimilarity(args.getShape().getBoundingBox(), queryPower, targetPower), fields );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Filter makeFilter(SpatialArgs args, BBoxFieldInfo fieldInfo) {
|
||||||
|
Query spatial = makeSpatialQuery(args, fieldInfo);
|
||||||
|
return new QueryWrapperFilter( spatial );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query makeQuery(SpatialArgs args, BBoxFieldInfo fieldInfo) {
|
||||||
|
BooleanQuery bq = new BooleanQuery();
|
||||||
|
Query spatial = makeSpatialQuery(args, fieldInfo);
|
||||||
|
bq.add(new ConstantScoreQuery(spatial), BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
// This part does the scoring
|
||||||
|
Query spatialRankingQuery = new FunctionQuery(makeValueSource(args, fieldInfo));
|
||||||
|
bq.add(spatialRankingQuery, BooleanClause.Occur.MUST);
|
||||||
|
return bq;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Query makeSpatialQuery(SpatialArgs args, BBoxFieldInfo fieldInfo) {
|
||||||
|
Rectangle bbox = args.getShape().getBoundingBox();
|
||||||
|
Query spatial = null;
|
||||||
|
|
||||||
|
// Useful for understanding Relations:
|
||||||
|
// http://edndoc.esri.com/arcsde/9.1/general_topics/understand_spatial_relations.htm
|
||||||
|
SpatialOperation op = args.getOperation();
|
||||||
|
if( op == SpatialOperation.BBoxIntersects ) spatial = makeIntersects(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.BBoxWithin ) spatial = makeWithin(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.Contains ) spatial = makeContains(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.Intersects ) spatial = makeIntersects(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.IsEqualTo ) spatial = makeEquals(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.IsDisjointTo ) spatial = makeDisjoint(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.IsWithin ) spatial = makeWithin(bbox, fieldInfo);
|
||||||
|
else if( op == SpatialOperation.Overlaps ) spatial = makeIntersects(bbox, fieldInfo);
|
||||||
|
else {
|
||||||
|
throw new UnsupportedSpatialOperation(op);
|
||||||
|
}
|
||||||
|
return spatial;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
//-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents that fully contain the input envelope.
|
||||||
|
*
|
||||||
|
* @return the spatial query
|
||||||
|
*/
|
||||||
|
Query makeContains(Rectangle bbox, BBoxFieldInfo fieldInfo) {
|
||||||
|
|
||||||
|
// general case
|
||||||
|
// docMinX <= queryExtent.getMinX() AND docMinY <= queryExtent.getMinY() AND docMaxX >= queryExtent.getMaxX() AND docMaxY >= queryExtent.getMaxY()
|
||||||
|
|
||||||
|
// Y conditions
|
||||||
|
// docMinY <= queryExtent.getMinY() AND docMaxY >= queryExtent.getMaxY()
|
||||||
|
Query qMinY = NumericRangeQuery.newDoubleRange(fieldInfo.minY, finfo.precisionStep, null, bbox.getMinY(), false, true);
|
||||||
|
Query qMaxY = NumericRangeQuery.newDoubleRange(fieldInfo.maxY, finfo.precisionStep, bbox.getMaxY(), null, true, false);
|
||||||
|
Query yConditions = this.makeQuery(new Query[]{qMinY, qMaxY}, BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
// X conditions
|
||||||
|
Query xConditions = null;
|
||||||
|
|
||||||
|
// queries that do not cross the date line
|
||||||
|
if (!bbox.getCrossesDateLine()) {
|
||||||
|
|
||||||
|
// 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 = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, null, bbox.getMinX(), false, true);
|
||||||
|
Query qMaxX = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, bbox.getMaxX(), null, true, false);
|
||||||
|
Query qMinMax = this.makeQuery(new Query[]{qMinX, qMaxX}, BooleanClause.Occur.MUST);
|
||||||
|
Query qNonXDL = this.makeXDL(false, qMinMax, fieldInfo);
|
||||||
|
|
||||||
|
// X Conditions for documents that cross the date line,
|
||||||
|
// 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 = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, null, bbox.getMinX(), false, true);
|
||||||
|
Query qXDLRight = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, bbox.getMaxX(), null, true, false);
|
||||||
|
Query qXDLLeftRight = this.makeQuery(new Query[]{qXDLLeft, qXDLRight}, BooleanClause.Occur.SHOULD);
|
||||||
|
Query qXDL = this.makeXDL(true, qXDLLeftRight, fieldInfo);
|
||||||
|
|
||||||
|
// apply the non-XDL and XDL conditions
|
||||||
|
xConditions = this.makeQuery(new Query[]{qNonXDL, qXDL}, BooleanClause.Occur.SHOULD);
|
||||||
|
|
||||||
|
// queries that cross the date line
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// No need to search for documents that do not cross the date line
|
||||||
|
|
||||||
|
// X Conditions for documents that cross the date line,
|
||||||
|
// 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 = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, null, bbox.getMinX(), false, true);
|
||||||
|
Query qXDLRight = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, bbox.getMaxX(), null, true, false);
|
||||||
|
Query qXDLLeftRight = this.makeQuery(new Query[]{qXDLLeft, qXDLRight}, BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
xConditions = this.makeXDL(true, qXDLLeftRight, fieldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// both X and Y conditions must occur
|
||||||
|
return this.makeQuery(new Query[]{xConditions, yConditions}, BooleanClause.Occur.MUST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents that are disjoint to the input envelope.
|
||||||
|
*
|
||||||
|
* @return the spatial query
|
||||||
|
*/
|
||||||
|
Query makeDisjoint(Rectangle bbox, BBoxFieldInfo fieldInfo) {
|
||||||
|
|
||||||
|
// general case
|
||||||
|
// docMinX > queryExtent.getMaxX() OR docMaxX < queryExtent.getMinX() OR docMinY > queryExtent.getMaxY() OR docMaxY < queryExtent.getMinY()
|
||||||
|
|
||||||
|
// Y conditions
|
||||||
|
// docMinY > queryExtent.getMaxY() OR docMaxY < queryExtent.getMinY()
|
||||||
|
Query qMinY = NumericRangeQuery.newDoubleRange(fieldInfo.minY, finfo.precisionStep, bbox.getMaxY(), null, false, false);
|
||||||
|
Query qMaxY = NumericRangeQuery.newDoubleRange(fieldInfo.maxY, finfo.precisionStep, null, bbox.getMinY(), false, false);
|
||||||
|
Query yConditions = this.makeQuery(new Query[]{qMinY, qMaxY}, BooleanClause.Occur.SHOULD);
|
||||||
|
|
||||||
|
// X conditions
|
||||||
|
Query xConditions = null;
|
||||||
|
|
||||||
|
// queries that do not cross the date line
|
||||||
|
if (!bbox.getCrossesDateLine()) {
|
||||||
|
|
||||||
|
// X Conditions for documents that do not cross the date line,
|
||||||
|
// docMinX > queryExtent.getMaxX() OR docMaxX < queryExtent.getMinX()
|
||||||
|
Query qMinX = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMaxX(), null, false, false);
|
||||||
|
Query qMaxX = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, bbox.getMinX(), false, false);
|
||||||
|
Query qMinMax = this.makeQuery(new Query[]{qMinX, qMaxX}, BooleanClause.Occur.SHOULD);
|
||||||
|
Query qNonXDL = this.makeXDL(false, qMinMax, fieldInfo);
|
||||||
|
|
||||||
|
// X Conditions for documents that cross the date line,
|
||||||
|
// both the left and right portions of the document must be disjoint to the query
|
||||||
|
// (docMinXLeft > queryExtent.getMaxX() OR docMaxXLeft < queryExtent.getMinX()) AND
|
||||||
|
// (docMinXRight > queryExtent.getMaxX() OR docMaxXRight < queryExtent.getMinX())
|
||||||
|
// 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 = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMaxX(), null, false, false);
|
||||||
|
Query qMaxXRight = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, bbox.getMinX(), false, false);
|
||||||
|
Query qLeftRight = this.makeQuery(new Query[]{qMinXLeft, qMaxXRight}, BooleanClause.Occur.MUST);
|
||||||
|
Query qXDL = this.makeXDL(true, qLeftRight, fieldInfo);
|
||||||
|
|
||||||
|
// apply the non-XDL and XDL conditions
|
||||||
|
xConditions = this.makeQuery(new Query[]{qNonXDL, qXDL}, BooleanClause.Occur.SHOULD);
|
||||||
|
|
||||||
|
// queries that cross the date line
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// X Conditions for documents that do not cross the date line,
|
||||||
|
// 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 = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, 180.0, null, false, false);
|
||||||
|
Query qMaxXLeft = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, bbox.getMinX(), false, false);
|
||||||
|
Query qMinXRight = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMaxX(), null, false, false);
|
||||||
|
Query qMaxXRight = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, -180.0, false, false);
|
||||||
|
Query qLeft = this.makeQuery(new Query[]{qMinXLeft, qMaxXLeft}, BooleanClause.Occur.SHOULD);
|
||||||
|
Query qRight = this.makeQuery(new Query[]{qMinXRight, qMaxXRight}, BooleanClause.Occur.SHOULD);
|
||||||
|
Query qLeftRight = this.makeQuery(new Query[]{qLeft, qRight}, BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
// No need to search for documents that do not cross the date line
|
||||||
|
|
||||||
|
xConditions = this.makeXDL(false, qLeftRight, fieldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// either X or Y conditions should occur
|
||||||
|
return this.makeQuery(new Query[]{xConditions, yConditions}, BooleanClause.Occur.SHOULD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents that equal the input envelope.
|
||||||
|
*
|
||||||
|
* @return the spatial query
|
||||||
|
*/
|
||||||
|
Query makeEquals(Rectangle bbox, BBoxFieldInfo fieldInfo) {
|
||||||
|
|
||||||
|
// docMinX = queryExtent.getMinX() AND docMinY = queryExtent.getMinY() AND docMaxX = queryExtent.getMaxX() AND docMaxY = queryExtent.getMaxY()
|
||||||
|
Query qMinX = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMinX(), bbox.getMinX(), true, true);
|
||||||
|
Query qMinY = NumericRangeQuery.newDoubleRange(fieldInfo.minY, finfo.precisionStep, bbox.getMinY(), bbox.getMinY(), true, true);
|
||||||
|
Query qMaxX = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, bbox.getMaxX(), bbox.getMaxX(), true, true);
|
||||||
|
Query qMaxY = NumericRangeQuery.newDoubleRange(fieldInfo.maxY, finfo.precisionStep, bbox.getMaxY(), bbox.getMaxY(), true, true);
|
||||||
|
BooleanQuery bq = new BooleanQuery();
|
||||||
|
bq.add(qMinX, BooleanClause.Occur.MUST);
|
||||||
|
bq.add(qMinY, BooleanClause.Occur.MUST);
|
||||||
|
bq.add(qMaxX, BooleanClause.Occur.MUST);
|
||||||
|
bq.add(qMaxY, BooleanClause.Occur.MUST);
|
||||||
|
return bq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents that intersect the input envelope.
|
||||||
|
*
|
||||||
|
* @return the spatial query
|
||||||
|
*/
|
||||||
|
Query makeIntersects(Rectangle bbox, BBoxFieldInfo fieldInfo) {
|
||||||
|
|
||||||
|
// the original intersects query does not work for envelopes that cross the date line,
|
||||||
|
// switch to a NOT Disjoint query
|
||||||
|
|
||||||
|
// MUST_NOT causes a problem when it's the only clause type within a BooleanQuery,
|
||||||
|
// to get round it we add all documents as a SHOULD
|
||||||
|
|
||||||
|
// there must be an envelope, it must not be disjoint
|
||||||
|
Query qDisjoint = makeDisjoint(bbox, fieldInfo);
|
||||||
|
Query qIsNonXDL = this.makeXDL(false, fieldInfo);
|
||||||
|
Query qIsXDL = this.makeXDL(true, fieldInfo);
|
||||||
|
Query qHasEnv = this.makeQuery(new Query[]{qIsNonXDL, qIsXDL}, BooleanClause.Occur.SHOULD);
|
||||||
|
BooleanQuery qNotDisjoint = new BooleanQuery();
|
||||||
|
qNotDisjoint.add(qHasEnv, BooleanClause.Occur.MUST);
|
||||||
|
qNotDisjoint.add(qDisjoint, BooleanClause.Occur.MUST_NOT);
|
||||||
|
|
||||||
|
//Query qDisjoint = makeDisjoint();
|
||||||
|
//BooleanQuery qNotDisjoint = new BooleanQuery();
|
||||||
|
//qNotDisjoint.add(new MatchAllDocsQuery(),BooleanClause.Occur.SHOULD);
|
||||||
|
//qNotDisjoint.add(qDisjoint,BooleanClause.Occur.MUST_NOT);
|
||||||
|
return qNotDisjoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a boolean query based upon a collection of queries and a logical operator.
|
||||||
|
*
|
||||||
|
* @param queries the query collection
|
||||||
|
* @param occur the logical operator
|
||||||
|
* @return the query
|
||||||
|
*/
|
||||||
|
BooleanQuery makeQuery(Query[] queries, BooleanClause.Occur occur) {
|
||||||
|
BooleanQuery bq = new BooleanQuery();
|
||||||
|
for (Query query : queries) {
|
||||||
|
bq.add(query, occur);
|
||||||
|
}
|
||||||
|
return bq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents are fully within the input envelope.
|
||||||
|
*
|
||||||
|
* @return the spatial query
|
||||||
|
*/
|
||||||
|
Query makeWithin(Rectangle bbox, BBoxFieldInfo fieldInfo) {
|
||||||
|
|
||||||
|
// general case
|
||||||
|
// docMinX >= queryExtent.getMinX() AND docMinY >= queryExtent.getMinY() AND docMaxX <= queryExtent.getMaxX() AND docMaxY <= queryExtent.getMaxY()
|
||||||
|
|
||||||
|
// Y conditions
|
||||||
|
// docMinY >= queryExtent.getMinY() AND docMaxY <= queryExtent.getMaxY()
|
||||||
|
Query qMinY = NumericRangeQuery.newDoubleRange(fieldInfo.minY, finfo.precisionStep, bbox.getMinY(), null, true, false);
|
||||||
|
Query qMaxY = NumericRangeQuery.newDoubleRange(fieldInfo.maxY, finfo.precisionStep, null, bbox.getMaxY(), false, true);
|
||||||
|
Query yConditions = this.makeQuery(new Query[]{qMinY, qMaxY}, BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
// X conditions
|
||||||
|
Query xConditions = null;
|
||||||
|
|
||||||
|
// X Conditions for documents that cross the date line,
|
||||||
|
// the left portion of the document must be within the left portion of the query,
|
||||||
|
// 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 = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMinX(), null, true, false);
|
||||||
|
Query qXDLRight = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, bbox.getMaxX(), false, true);
|
||||||
|
Query qXDLLeftRight = this.makeQuery(new Query[]{qXDLLeft, qXDLRight}, BooleanClause.Occur.MUST);
|
||||||
|
Query qXDL = this.makeXDL(true, qXDLLeftRight, fieldInfo);
|
||||||
|
|
||||||
|
// queries that do not cross the date line
|
||||||
|
if (!bbox.getCrossesDateLine()) {
|
||||||
|
|
||||||
|
// X Conditions for documents that do not cross the date line,
|
||||||
|
// docMinX >= queryExtent.getMinX() AND docMaxX <= queryExtent.getMaxX()
|
||||||
|
Query qMinX = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMinX(), null, true, false);
|
||||||
|
Query qMaxX = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, bbox.getMaxX(), false, true);
|
||||||
|
Query qMinMax = this.makeQuery(new Query[]{qMinX, qMaxX}, BooleanClause.Occur.MUST);
|
||||||
|
Query qNonXDL = this.makeXDL(false, qMinMax, fieldInfo);
|
||||||
|
|
||||||
|
// apply the non-XDL or XDL X conditions
|
||||||
|
if ((bbox.getMinX() <= -180.0) && bbox.getMaxX() >= 180.0) {
|
||||||
|
xConditions = this.makeQuery(new Query[]{qNonXDL, qXDL}, BooleanClause.Occur.SHOULD);
|
||||||
|
} else {
|
||||||
|
xConditions = qNonXDL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// queries that cross the date line
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// X Conditions for documents that do not cross the date line
|
||||||
|
|
||||||
|
// the document should be within the left portion of the query
|
||||||
|
// docMinX >= queryExtent.getMinX() AND docMaxX <= 180.0
|
||||||
|
Query qMinXLeft = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, bbox.getMinX(), null, true, false);
|
||||||
|
Query qMaxXLeft = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, 180.0, false, true);
|
||||||
|
Query qLeft = this.makeQuery(new Query[]{qMinXLeft, qMaxXLeft}, BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
// the document should be within the right portion of the query
|
||||||
|
// docMinX >= -180.0 AND docMaxX <= queryExtent.getMaxX()
|
||||||
|
Query qMinXRight = NumericRangeQuery.newDoubleRange(fieldInfo.minX, finfo.precisionStep, -180.0, null, true, false);
|
||||||
|
Query qMaxXRight = NumericRangeQuery.newDoubleRange(fieldInfo.maxX, finfo.precisionStep, null, bbox.getMaxX(), false, true);
|
||||||
|
Query qRight = this.makeQuery(new Query[]{qMinXRight, qMaxXRight}, BooleanClause.Occur.MUST);
|
||||||
|
|
||||||
|
// either left or right conditions should occur,
|
||||||
|
// apply the left and right conditions to documents that do not cross the date line
|
||||||
|
Query qLeftRight = this.makeQuery(new Query[]{qLeft, qRight}, BooleanClause.Occur.SHOULD);
|
||||||
|
Query qNonXDL = this.makeXDL(false, qLeftRight, fieldInfo);
|
||||||
|
|
||||||
|
// apply the non-XDL and XDL conditions
|
||||||
|
xConditions = this.makeQuery(new Query[]{qNonXDL, qXDL}, BooleanClause.Occur.SHOULD);
|
||||||
|
}
|
||||||
|
|
||||||
|
// both X and Y conditions must occur
|
||||||
|
return this.makeQuery(new Query[]{xConditions, yConditions}, BooleanClause.Occur.MUST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents that do or do not cross the date line.
|
||||||
|
*
|
||||||
|
* @param crossedDateLine <code>true</true> for documents that cross the date line
|
||||||
|
* @return the query
|
||||||
|
*/
|
||||||
|
Query makeXDL(boolean crossedDateLine, BBoxFieldInfo fieldInfo) {
|
||||||
|
// The 'T' and 'F' values match solr fields
|
||||||
|
return new TermQuery(new Term(fieldInfo.xdl, crossedDateLine ? "T" : "F"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a query to retrieve documents that do or do not cross the date line
|
||||||
|
* and match the supplied spatial query.
|
||||||
|
*
|
||||||
|
* @param crossedDateLine <code>true</true> for documents that cross the date line
|
||||||
|
* @param query the spatial query
|
||||||
|
* @return the query
|
||||||
|
*/
|
||||||
|
Query makeXDL(boolean crossedDateLine, Query query, BBoxFieldInfo fieldInfo) {
|
||||||
|
BooleanQuery bq = new BooleanQuery();
|
||||||
|
bq.add(this.makeXDL(crossedDateLine, fieldInfo), BooleanClause.Occur.MUST);
|
||||||
|
bq.add(query, BooleanClause.Occur.MUST);
|
||||||
|
return bq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#id name shape
|
||||||
|
C5 CenterAt5 -5 -5 5 5
|
||||||
|
C10 CenterAt10 -10 -10 10 10
|
||||||
|
NW15 NorthWest 15 15 20 20
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
C5 @ IsWithin(-6 -6 6 6)
|
||||||
|
C5 @ BBoxWithin(-6 -6 6 6)
|
||||||
|
C10 @ Contains(-6 -6 6 6)
|
||||||
|
C10 @ IsEqualTo(-10 -10 10 10)
|
||||||
|
C5 C10 @ Intersects(-2 -2 2 2)
|
||||||
|
C5 C10 @ Overlaps(-2 -2 2 2)
|
||||||
|
C5 C10 @ BBoxIntersects(-2 -2 2 2)
|
||||||
|
NW15 @ IsDisjointTo(-10 -10 10 10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,22 @@ public abstract class SpatialTestCase extends LuceneTestCase {
|
||||||
this.numFound = numFound;
|
this.numFound = numFound;
|
||||||
this.results = results;
|
this.results = results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StringBuilder toDebugString() {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append("found: ").append(numFound).append('[');
|
||||||
|
for(SearchResult r : results) {
|
||||||
|
String id = r.document.get("id");
|
||||||
|
str.append(id).append(", ");
|
||||||
|
}
|
||||||
|
str.append(']');
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[found:"+numFound+" "+results+"]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class SearchResult {
|
protected static class SearchResult {
|
||||||
|
@ -120,6 +136,11 @@ public abstract class SpatialTestCase extends LuceneTestCase {
|
||||||
this.score = score;
|
this.score = score;
|
||||||
this.document = document;
|
this.document = document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "["+score+"="+document+"]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ import java.util.logging.Logger;
|
||||||
|
|
||||||
public abstract class StrategyTestCase<T extends SpatialFieldInfo> extends SpatialTestCase {
|
public abstract class StrategyTestCase<T extends SpatialFieldInfo> extends SpatialTestCase {
|
||||||
|
|
||||||
|
public static final String DATA_SIMPLE_BBOX = "simple-bbox.txt";
|
||||||
public static final String DATA_STATES_POLY = "states-poly.txt";
|
public static final String DATA_STATES_POLY = "states-poly.txt";
|
||||||
public static final String DATA_STATES_BBOX = "states-bbox.txt";
|
public static final String DATA_STATES_BBOX = "states-bbox.txt";
|
||||||
public static final String DATA_COUNTRIES_POLY = "countries-poly.txt";
|
public static final String DATA_COUNTRIES_POLY = "countries-poly.txt";
|
||||||
|
@ -44,8 +45,8 @@ public abstract class StrategyTestCase<T extends SpatialFieldInfo> extends Spati
|
||||||
|
|
||||||
public static final String QTEST_States_IsWithin_BBox = "states-IsWithin-BBox.txt";
|
public static final String QTEST_States_IsWithin_BBox = "states-IsWithin-BBox.txt";
|
||||||
public static final String QTEST_States_Intersects_BBox = "states-Intersects-BBox.txt";
|
public static final String QTEST_States_Intersects_BBox = "states-Intersects-BBox.txt";
|
||||||
|
|
||||||
public static final String QTEST_Cities_IsWithin_BBox = "cities-IsWithin-BBox.txt";
|
public static final String QTEST_Cities_IsWithin_BBox = "cities-IsWithin-BBox.txt";
|
||||||
|
public static final String QTEST_Simple_Queries_BBox = "simple-Queries-BBox.txt";
|
||||||
|
|
||||||
private Logger log = Logger.getLogger(getClass().getName());
|
private Logger log = Logger.getLogger(getClass().getName());
|
||||||
|
|
||||||
|
@ -112,8 +113,12 @@ public abstract class StrategyTestCase<T extends SpatialFieldInfo> extends Spati
|
||||||
Iterator<String> ids = q.ids.iterator();
|
Iterator<String> ids = q.ids.iterator();
|
||||||
for (SearchResult r : got.results) {
|
for (SearchResult r : got.results) {
|
||||||
String id = r.document.get("id");
|
String id = r.document.get("id");
|
||||||
|
if(!ids.hasNext()) {
|
||||||
|
Assert.fail(msg + " :: Did not get enough results. Expect" + q.ids+", got: "+got.toDebugString());
|
||||||
|
}
|
||||||
Assert.assertEquals( "out of order: " + msg, ids.next(), id);
|
Assert.assertEquals( "out of order: " + msg, ids.next(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ids.hasNext()) {
|
if (ids.hasNext()) {
|
||||||
Assert.fail(msg + " :: expect more results then we got: " + ids.next());
|
Assert.fail(msg + " :: expect more results then we got: " + ids.next());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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.bbox;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
||||||
|
import org.apache.lucene.spatial.SpatialMatchConcern;
|
||||||
|
import org.apache.lucene.spatial.StrategyTestCase;
|
||||||
|
import org.apache.lucene.spatial.util.NumericFieldInfo;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TestBBoxStrategy extends StrategyTestCase<BBoxFieldInfo> {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
this.ctx = SimpleSpatialContext.GEO_KM;
|
||||||
|
|
||||||
|
BBoxStrategy s = new BBoxStrategy(ctx);
|
||||||
|
s.finfo = new NumericFieldInfo();
|
||||||
|
|
||||||
|
this.strategy = s;
|
||||||
|
this.fieldInfo = new BBoxFieldInfo("bbox");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicOperaions() throws IOException {
|
||||||
|
getAddAndVerifyIndexedDocuments(DATA_SIMPLE_BBOX);
|
||||||
|
|
||||||
|
executeQueries(SpatialMatchConcern.EXACT, QTEST_Simple_Queries_BBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatesBBox() throws IOException {
|
||||||
|
getAddAndVerifyIndexedDocuments(DATA_STATES_BBOX);
|
||||||
|
|
||||||
|
executeQueries(SpatialMatchConcern.FILTER, QTEST_States_IsWithin_BBox);
|
||||||
|
executeQueries(SpatialMatchConcern.FILTER, QTEST_States_Intersects_BBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCitiesWithinBBox() throws IOException {
|
||||||
|
getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
|
||||||
|
|
||||||
|
executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_IsWithin_BBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue