mirror of https://github.com/apache/lucene.git
LUCENE-5714, LUCENE-5779: Enhance BBoxStrategy & Overlap similarity. Configurable docValues / index usage.
Add new ShapeAreaValueSource. git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1608793 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
beeeba528f
commit
7e0aff8dab
|
@ -24,9 +24,6 @@ New Features
|
|||
implemented in the spatial module as DateRangePrefixTree used with
|
||||
NumberRangePrefixTreeStrategy. (David Smiley)
|
||||
|
||||
* LUCENE-4175: Index and search rectangles with spatial BBoxSpatialStrategy.
|
||||
Sort documents by relative overlap of query areas. (Ryan McKinley)
|
||||
|
||||
API Changes
|
||||
|
||||
* LUCENE-4535: oal.util.FilterIterator is now an internal API.
|
||||
|
@ -103,6 +100,10 @@ New Features
|
|||
* LUCENE-5801: Added (back) OrdinalMappingAtomicReader for merging search
|
||||
indexes that contain category ordinals from separate taxonomy indexes.
|
||||
(Nicola Buso via Shai Erera)
|
||||
|
||||
* LUCENE-4175, LUCENE-5714, LUCENE-5779: Index and search rectangles with spatial
|
||||
BBoxSpatialStrategy using most predicates. Sort documents by relative overlap
|
||||
of query areas or just by indexed shape area. (Ryan McKinley, David Smiley)
|
||||
|
||||
API Changes
|
||||
|
||||
|
|
|
@ -1,217 +0,0 @@
|
|||
/*
|
||||
* 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/>
|
||||
* Based on Geoportal's
|
||||
* <a href="http://geoportal.svn.sourceforge.net/svnroot/geoportal/Geoportal/trunk/src/com/esri/gpt/catalog/lucene/SpatialRankingValueSource.java">
|
||||
* SpatialRankingValueSource</a>.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
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(null);
|
||||
|
||||
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(null);
|
||||
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,244 @@
|
|||
/*
|
||||
* 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.shape.Rectangle;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
|
||||
/**
|
||||
* The algorithm is implemented as envelope on envelope (rect on rect) overlays rather than
|
||||
* complex polygon on complex polygon overlays.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* Spatial relevance scoring algorithm:
|
||||
* <DL>
|
||||
* <DT>queryArea</DT> <DD>the area of the input query envelope</DD>
|
||||
* <DT>targetArea</DT> <DD>the area of the target envelope (per Lucene document)</DD>
|
||||
* <DT>intersectionArea</DT> <DD>the area of the intersection between the query and target envelopes</DD>
|
||||
* <DT>queryTargetProportion</DT> <DD>A 0-1 factor that divides the score proportion between query and target.
|
||||
* 0.5 is evenly.</DD>
|
||||
*
|
||||
* <DT>queryRatio</DT> <DD>intersectionArea / queryArea; (see note)</DD>
|
||||
* <DT>targetRatio</DT> <DD>intersectionArea / targetArea; (see note)</DD>
|
||||
* <DT>queryFactor</DT> <DD>queryRatio * queryTargetProportion;</DD>
|
||||
* <DT>targetFactor</DT> <DD>targetRatio * (1 - queryTargetProportion);</DD>
|
||||
* <DT>score</DT> <DD>queryFactor + targetFactor;</DD>
|
||||
* </DL>
|
||||
* Additionally, note that an optional minimum side length {@code minSideLength} may be used whenever an
|
||||
* area is calculated (queryArea, targetArea, intersectionArea). This allows for points or horizontal/vertical lines
|
||||
* to be used as the query shape and in such case the descending order should have smallest boxes up front. Without
|
||||
* this, a point or line query shape typically scores everything with the same value since there is 0 area.
|
||||
* <p />
|
||||
* Note: The actual computation of queryRatio and targetRatio is more complicated so that it considers
|
||||
* points and lines. Lines have the ratio of overlap, and points are either 1.0 or 0.0 depending on whether
|
||||
* it intersects or not.
|
||||
* <p />
|
||||
* Originally based on Geoportal's
|
||||
* <a href="http://geoportal.svn.sourceforge.net/svnroot/geoportal/Geoportal/trunk/src/com/esri/gpt/catalog/lucene/SpatialRankingValueSource.java">
|
||||
* SpatialRankingValueSource</a> but modified quite a bit. GeoPortal's algorithm will yield a score of 0
|
||||
* if either a line or point is compared, and it's doesn't output a 0-1 normalized score (it multiplies the factors),
|
||||
* and it doesn't support minSideLength, and it had dateline bugs.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class BBoxOverlapRatioValueSource extends BBoxSimilarityValueSource {
|
||||
|
||||
private final boolean isGeo;//-180/+180 degrees (not part of identity; attached to parent strategy/field)
|
||||
|
||||
private final Rectangle queryExtent;
|
||||
private final double queryArea;//not part of identity
|
||||
|
||||
private final double minSideLength;
|
||||
|
||||
private final double queryTargetProportion;
|
||||
|
||||
//TODO option to compute geodetic area
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rectValueSource mandatory; source of rectangles
|
||||
* @param isGeo True if ctx.isGeo() and thus dateline issues should be attended to
|
||||
* @param queryExtent mandatory; the query rectangle
|
||||
* @param queryTargetProportion see class javadocs. Between 0 and 1.
|
||||
* @param minSideLength see class javadocs. 0.0 will effectively disable.
|
||||
*/
|
||||
public BBoxOverlapRatioValueSource(ValueSource rectValueSource, boolean isGeo, Rectangle queryExtent,
|
||||
double queryTargetProportion, double minSideLength) {
|
||||
super(rectValueSource);
|
||||
this.isGeo = isGeo;
|
||||
this.minSideLength = minSideLength;
|
||||
this.queryExtent = queryExtent;
|
||||
this.queryArea = calcArea(queryExtent.getWidth(), queryExtent.getHeight());
|
||||
assert queryArea >= 0;
|
||||
this.queryTargetProportion = queryTargetProportion;
|
||||
if (queryTargetProportion < 0 || queryTargetProportion > 1.0)
|
||||
throw new IllegalArgumentException("queryTargetProportion must be >= 0 and <= 1");
|
||||
}
|
||||
|
||||
/** Construct with 75% weighting towards target (roughly GeoPortal's default), geo degrees assumed, no
|
||||
* minimum side length. */
|
||||
public BBoxOverlapRatioValueSource(ValueSource rectValueSource, Rectangle queryExtent) {
|
||||
this(rectValueSource, true, queryExtent, 0.25, 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
BBoxOverlapRatioValueSource that = (BBoxOverlapRatioValueSource) o;
|
||||
|
||||
if (Double.compare(that.minSideLength, minSideLength) != 0) return false;
|
||||
if (Double.compare(that.queryTargetProportion, queryTargetProportion) != 0) return false;
|
||||
if (!queryExtent.equals(that.queryExtent)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
long temp;
|
||||
result = 31 * result + queryExtent.hashCode();
|
||||
temp = Double.doubleToLongBits(minSideLength);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(queryTargetProportion);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String similarityDescription() {
|
||||
return queryExtent.toString() + "," + queryTargetProportion;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double score(Rectangle target, Explanation exp) {
|
||||
// calculate "height": the intersection height between two boxes.
|
||||
double top = Math.min(queryExtent.getMaxY(), target.getMaxY());
|
||||
double bottom = Math.max(queryExtent.getMinY(), target.getMinY());
|
||||
double height = top - bottom;
|
||||
if (height < 0)
|
||||
return 0;//no intersection
|
||||
|
||||
// calculate "width": the intersection width between two boxes.
|
||||
double width = 0;
|
||||
{
|
||||
Rectangle a = queryExtent;
|
||||
Rectangle b = target;
|
||||
if (a.getCrossesDateLine() == b.getCrossesDateLine()) {
|
||||
//both either cross or don't
|
||||
double left = Math.max(a.getMinX(), b.getMinX());
|
||||
double right = Math.min(a.getMaxX(), b.getMaxX());
|
||||
if (!a.getCrossesDateLine()) {//both don't
|
||||
if (left <= right) {
|
||||
width = right - left;
|
||||
} else if (isGeo && (Math.abs(a.getMinX()) == 180 || Math.abs(a.getMaxX()) == 180)
|
||||
&& (Math.abs(b.getMinX()) == 180 || Math.abs(b.getMaxX()) == 180)) {
|
||||
width = 0;//both adjacent to dateline
|
||||
} else {
|
||||
return 0;//no intersection
|
||||
}
|
||||
} else {//both cross
|
||||
width = right - left + 360;
|
||||
}
|
||||
} else {
|
||||
if (!a.getCrossesDateLine()) {//then flip
|
||||
a = target;
|
||||
b = queryExtent;
|
||||
}
|
||||
//a crosses, b doesn't
|
||||
double qryWestLeft = Math.max(a.getMinX(), b.getMinX());
|
||||
double qryWestRight = b.getMaxX();
|
||||
if (qryWestLeft < qryWestRight)
|
||||
width += qryWestRight - qryWestLeft;
|
||||
|
||||
double qryEastLeft = b.getMinX();
|
||||
double qryEastRight = Math.min(a.getMaxX(), b.getMaxX());
|
||||
if (qryEastLeft < qryEastRight)
|
||||
width += qryEastRight - qryEastLeft;
|
||||
|
||||
if (qryWestLeft > qryWestRight && qryEastLeft > qryEastRight)
|
||||
return 0;//no intersection
|
||||
}
|
||||
}
|
||||
|
||||
// calculate queryRatio and targetRatio
|
||||
double intersectionArea = calcArea(width, height);
|
||||
double queryRatio;
|
||||
if (queryArea > 0) {
|
||||
queryRatio = intersectionArea / queryArea;
|
||||
} else if (queryExtent.getHeight() > 0) {//vert line
|
||||
queryRatio = height / queryExtent.getHeight();
|
||||
} else if (queryExtent.getWidth() > 0) {//horiz line
|
||||
queryRatio = width / queryExtent.getWidth();
|
||||
} else {
|
||||
queryRatio = queryExtent.relate(target).intersects() ? 1 : 0;//could be optimized
|
||||
}
|
||||
|
||||
double targetArea = calcArea(target.getWidth(), target.getHeight());
|
||||
assert targetArea >= 0;
|
||||
double targetRatio;
|
||||
if (targetArea > 0) {
|
||||
targetRatio = intersectionArea / targetArea;
|
||||
} else if (target.getHeight() > 0) {//vert line
|
||||
targetRatio = height / target.getHeight();
|
||||
} else if (target.getWidth() > 0) {//horiz line
|
||||
targetRatio = width / target.getWidth();
|
||||
} else {
|
||||
targetRatio = target.relate(queryExtent).intersects() ? 1 : 0;//could be optimized
|
||||
}
|
||||
assert queryRatio >= 0 && queryRatio <= 1 : queryRatio;
|
||||
assert targetRatio >= 0 && targetRatio <= 1 : targetRatio;
|
||||
|
||||
// combine ratios into a score
|
||||
|
||||
double queryFactor = queryRatio * queryTargetProportion;
|
||||
double targetFactor = targetRatio * (1.0 - queryTargetProportion);
|
||||
double score = queryFactor + targetFactor;
|
||||
|
||||
if (exp!=null) {
|
||||
exp.setValue((float)score);
|
||||
exp.setDescription(this.getClass().getSimpleName()+": queryFactor + targetFactor");
|
||||
|
||||
Explanation e;//tmp
|
||||
|
||||
String minSideDesc = minSideLength > 0.0 ? " (minSide="+minSideLength+")" : "";
|
||||
|
||||
exp.addDetail( e = new Explanation((float)intersectionArea, "IntersectionArea" + minSideDesc));
|
||||
e.addDetail(new Explanation((float)width, "width"));
|
||||
e.addDetail(new Explanation((float)height, "height"));
|
||||
e.addDetail(new Explanation((float)queryTargetProportion, "queryTargetProportion"));
|
||||
|
||||
exp.addDetail( e = new Explanation((float)queryFactor, "queryFactor"));
|
||||
e.addDetail(new Explanation((float)queryRatio, "ratio"));
|
||||
e.addDetail(new Explanation((float)queryArea, "area of " + queryExtent + minSideDesc));
|
||||
|
||||
exp.addDetail( e = new Explanation((float)targetFactor, "targetFactor"));
|
||||
e.addDetail(new Explanation((float)targetRatio, "ratio"));
|
||||
e.addDetail(new Explanation((float)targetArea, "area of " + target + minSideDesc));
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/** Calculates the area while applying the minimum side length. */
|
||||
private double calcArea(double width, double height) {
|
||||
return Math.max(minSideLength, width) * Math.max(minSideLength, height);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Abstraction of the calculation used to determine how similar two Bounding Boxes are.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public interface BBoxSimilarity {
|
||||
|
||||
public double score(Rectangle extent, Explanation exp);
|
||||
}
|
|
@ -18,119 +18,99 @@ package org.apache.lucene.spatial.bbox;
|
|||
*/
|
||||
|
||||
import com.spatial4j.core.shape.Rectangle;
|
||||
import org.apache.lucene.index.AtomicReader;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.index.DocValues;
|
||||
import org.apache.lucene.index.NumericDocValues;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of the Lucene ValueSource model to support spatial relevance ranking.
|
||||
* A base class for calculating a spatial relevance rank per document from a provided
|
||||
* {@link ValueSource} in which {@link FunctionValues#objectVal(int)} returns a {@link
|
||||
* com.spatial4j.core.shape.Rectangle}.
|
||||
* <p/>
|
||||
* Implementers: remember to implement equals & hashCode if you have
|
||||
* fields!
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class BBoxSimilarityValueSource extends ValueSource {
|
||||
public abstract class BBoxSimilarityValueSource extends ValueSource {
|
||||
|
||||
private final BBoxStrategy strategy;
|
||||
private final BBoxSimilarity similarity;
|
||||
private final ValueSource bboxValueSource;
|
||||
|
||||
public BBoxSimilarityValueSource(BBoxStrategy strategy, BBoxSimilarity similarity) {
|
||||
this.strategy = strategy;
|
||||
this.similarity = similarity;
|
||||
public BBoxSimilarityValueSource(ValueSource bboxValueSource) {
|
||||
this.bboxValueSource = bboxValueSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
|
||||
bboxValueSource.createWeight(context, searcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ValueSource description.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
@Override
|
||||
public String description() {
|
||||
return "BBoxSimilarityValueSource(" + similarity + ")";
|
||||
return getClass().getSimpleName()+"(" + bboxValueSource.description() + "," + similarityDescription() + ")";
|
||||
}
|
||||
|
||||
/** A comma-separated list of configurable items of the subclass to put into {@link #description()}. */
|
||||
protected abstract String similarityDescription();
|
||||
|
||||
/**
|
||||
* Returns the DocValues used by the function query.
|
||||
*
|
||||
* @param readerContext the AtomicReaderContext which holds an AtomicReader
|
||||
* @return the values
|
||||
*/
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
AtomicReader reader = readerContext.reader();
|
||||
final NumericDocValues minX = DocValues.getNumeric(reader, strategy.field_minX);
|
||||
final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY);
|
||||
final NumericDocValues maxX = DocValues.getNumeric(reader, strategy.field_maxX);
|
||||
final NumericDocValues maxY = DocValues.getNumeric(reader, strategy.field_maxY);
|
||||
|
||||
final Bits validMinX = DocValues.getDocsWithField(reader, strategy.field_minX);
|
||||
final Bits validMaxX = DocValues.getDocsWithField(reader, strategy.field_maxX);
|
||||
final FunctionValues shapeValues = bboxValueSource.getValues(context, readerContext);
|
||||
|
||||
return new FunctionValues() {
|
||||
//reused
|
||||
Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
|
||||
return new DoubleDocValues(this) {
|
||||
@Override
|
||||
public double doubleVal(int doc) {
|
||||
//? limit to Rect or call getBoundingBox()? latter would encourage bad practice
|
||||
final Rectangle rect = (Rectangle) shapeValues.objectVal(doc);
|
||||
return rect==null ? 0 : score(rect, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatVal(int doc) {
|
||||
double minXVal = Double.longBitsToDouble(minX.get(doc));
|
||||
double maxXVal = Double.longBitsToDouble(maxX.get(doc));
|
||||
// make sure it has minX and area
|
||||
if ((minXVal != 0 || validMinX.get(doc)) && (maxXVal != 0 || validMaxX.get(doc))) {
|
||||
rect.reset(
|
||||
minXVal, maxXVal,
|
||||
Double.longBitsToDouble(minY.get(doc)), Double.longBitsToDouble(maxY.get(doc)));
|
||||
return (float) similarity.score(rect, null);
|
||||
} else {
|
||||
return (float) similarity.score(null, null);
|
||||
}
|
||||
public boolean exists(int doc) {
|
||||
return shapeValues.exists(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(int doc) {
|
||||
// make sure it has minX and area
|
||||
if (validMinX.get(doc) && validMaxX.get(doc)) {
|
||||
rect.reset(
|
||||
Double.longBitsToDouble(minX.get(doc)), Double.longBitsToDouble(maxX.get(doc)),
|
||||
Double.longBitsToDouble(minY.get(doc)), Double.longBitsToDouble(maxY.get(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);
|
||||
final Rectangle rect = (Rectangle) shapeValues.objectVal(doc);
|
||||
if (rect == null)
|
||||
return new Explanation(0, "no rect");
|
||||
Explanation exp = new Explanation();
|
||||
score(rect, exp);
|
||||
return exp;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Return a relevancy score. If {@code exp} is provided then diagnostic information is added.
|
||||
* @param rect The indexed rectangle; not null.
|
||||
* @param exp Optional diagnostic holder.
|
||||
* @return a score.
|
||||
*/
|
||||
protected abstract double score(Rectangle rect, Explanation exp);
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o.getClass() != BBoxSimilarityValueSource.class) {
|
||||
return false;
|
||||
}
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;//same class
|
||||
|
||||
BBoxSimilarityValueSource other = (BBoxSimilarityValueSource) o;
|
||||
return similarity.equals(other.similarity);
|
||||
BBoxSimilarityValueSource that = (BBoxSimilarityValueSource) o;
|
||||
|
||||
if (!bboxValueSource.equals(that.bboxValueSource)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return BBoxSimilarityValueSource.class.hashCode() + similarity.hashCode();
|
||||
return bboxValueSource.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ import org.apache.lucene.document.Field;
|
|||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.AtomicReader;
|
||||
import org.apache.lucene.index.FieldInfo;
|
||||
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;
|
||||
|
@ -41,6 +41,9 @@ 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.apache.lucene.spatial.util.DistanceToShapeValueSource;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -62,7 +65,7 @@ import org.apache.lucene.spatial.query.UnsupportedSpatialOperation;
|
|||
* and a boolean to mark a dateline cross. Depending on the particular {@link
|
||||
* SpatialOperation}s, there is a variety of {@link NumericRangeQuery}s to be
|
||||
* done.
|
||||
* The {@link #makeBBoxAreaSimilarityValueSource(com.spatial4j.core.shape.Rectangle)}
|
||||
* The {@link #makeOverlapRatioValueSource(com.spatial4j.core.shape.Rectangle, double)}
|
||||
* works by calculating the query bbox overlap percentage against the indexed
|
||||
* shape overlap percentage. The indexed shape's coordinates are retrieved from
|
||||
* {@link AtomicReader#getNumericDocValues}
|
||||
|
@ -77,20 +80,22 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
public static final String SUFFIX_MAXY = "__maxY";
|
||||
public static final String SUFFIX_XDL = "__xdl";
|
||||
|
||||
private static BytesRef T_BYTES = new BytesRef("T");//same as Solr BoolField
|
||||
private static BytesRef F_BYTES = new BytesRef("F");//same as Solr BoolField
|
||||
|
||||
/*
|
||||
* 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 final String field_bbox;
|
||||
public final String field_minX;
|
||||
public final String field_minY;
|
||||
public final String field_maxX;
|
||||
public final String field_maxY;
|
||||
public final String field_xdl; // crosses dateline
|
||||
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
|
||||
|
||||
public double queryPower = 1.0;
|
||||
public double targetPower = 1.0f;
|
||||
public int precisionStep = 8; // same as solr default
|
||||
protected FieldType fieldType;//for the 4 numbers
|
||||
protected FieldType xdlFieldType;
|
||||
|
||||
public BBoxStrategy(SpatialContext ctx, String fieldNamePrefix) {
|
||||
super(ctx, fieldNamePrefix);
|
||||
|
@ -100,12 +105,32 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
field_minY = fieldNamePrefix + SUFFIX_MINY;
|
||||
field_maxY = fieldNamePrefix + SUFFIX_MAXY;
|
||||
field_xdl = fieldNamePrefix + SUFFIX_XDL;
|
||||
|
||||
FieldType fieldType = new FieldType(DoubleField.TYPE_NOT_STORED);
|
||||
fieldType.setNumericPrecisionStep(8);//Solr's default
|
||||
fieldType.setDocValueType(FieldInfo.DocValuesType.NUMERIC);
|
||||
setFieldType(fieldType);
|
||||
}
|
||||
|
||||
public void setPrecisionStep( int p ) {
|
||||
precisionStep = p;
|
||||
if (precisionStep<=0 || precisionStep>=64)
|
||||
precisionStep=Integer.MAX_VALUE;
|
||||
private int getPrecisionStep() {
|
||||
return fieldType.numericPrecisionStep();
|
||||
}
|
||||
|
||||
public FieldType getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
public void setFieldType(FieldType fieldType) {
|
||||
fieldType.freeze();
|
||||
this.fieldType = fieldType;
|
||||
//only double's supported right now
|
||||
if (fieldType.numericType() != FieldType.NumericType.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.setIndexed(fieldType.indexed());
|
||||
xdlFieldType.freeze();
|
||||
}
|
||||
|
||||
//---------------------------------
|
||||
|
@ -119,33 +144,70 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
throw new UnsupportedOperationException("Can only index a Rectangle, not " + shape);
|
||||
}
|
||||
|
||||
/** Field subclass circumventing Field limitations. This one instance can optionally index, store,
|
||||
* or have 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
|
||||
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.NumericType.DOUBLE)
|
||||
return Double.doubleToLongBits(number.doubleValue());
|
||||
if (fieldType().numericType() == FieldType.NumericType.FLOAT)
|
||||
return Float.floatToIntBits(number.floatValue());
|
||||
return number.longValue();
|
||||
}
|
||||
}
|
||||
|
||||
public Field[] createIndexableFields(Rectangle bbox) {
|
||||
FieldType doubleFieldType = new FieldType(DoubleField.TYPE_NOT_STORED);
|
||||
doubleFieldType.setNumericPrecisionStep(precisionStep);
|
||||
Field[] fields = new Field[5];
|
||||
fields[0] = new DoubleField(field_minX, bbox.getMinX(), doubleFieldType);
|
||||
fields[1] = new DoubleField(field_maxX, bbox.getMaxX(), doubleFieldType);
|
||||
fields[2] = new DoubleField(field_minY, bbox.getMinY(), doubleFieldType);
|
||||
fields[3] = new DoubleField(field_maxY, bbox.getMaxY(), doubleFieldType);
|
||||
fields[4] = new Field( field_xdl, bbox.getCrossesDateLine()?"T":"F", StringField.TYPE_NOT_STORED);
|
||||
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);
|
||||
return fields;
|
||||
}
|
||||
|
||||
//---------------------------------
|
||||
// Query Builder
|
||||
// Value Source / Relevancy
|
||||
//---------------------------------
|
||||
|
||||
/**
|
||||
* Provides access to each rectangle per document as a ValueSource in which
|
||||
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)} returns a {@link
|
||||
* Shape}.
|
||||
*/ //TODO raise to SpatialStrategy
|
||||
public ValueSource makeShapeValueSource() {
|
||||
return new BBoxValueSource(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
|
||||
return new BBoxSimilarityValueSource(
|
||||
this, new DistanceSimilarity(this.getSpatialContext(), queryPoint, multiplier));
|
||||
//TODO if makeShapeValueSource gets lifted to the top; this could become a generic impl.
|
||||
return new DistanceToShapeValueSource(makeShapeValueSource(), queryPoint, multiplier, ctx);
|
||||
}
|
||||
|
||||
public ValueSource makeBBoxAreaSimilarityValueSource(Rectangle queryBox) {
|
||||
return new BBoxSimilarityValueSource(
|
||||
this, new AreaSimilarity(queryBox, queryPower, targetPower));
|
||||
/** Returns a similarity based on {@link BBoxOverlapRatioValueSource}. This is just a
|
||||
* convenience method. */
|
||||
public ValueSource makeOverlapRatioValueSource(Rectangle queryBox, double queryTargetProportion) {
|
||||
return new BBoxOverlapRatioValueSource(
|
||||
makeShapeValueSource(), ctx.isGeo(), queryBox, queryTargetProportion, 0.0);
|
||||
}
|
||||
|
||||
//---------------------------------
|
||||
// Query / Filter Building
|
||||
//---------------------------------
|
||||
|
||||
@Override
|
||||
public Filter makeFilter(SpatialArgs args) {
|
||||
return new QueryWrapperFilter(makeSpatialQuery(args));
|
||||
|
@ -156,17 +218,10 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
return new ConstantScoreQuery(makeSpatialQuery(args));
|
||||
}
|
||||
|
||||
public Query makeQueryWithValueSource(SpatialArgs args, ValueSource valueSource) {
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
Query spatial = makeSpatialQuery(args);
|
||||
bq.add(new ConstantScoreQuery(spatial), BooleanClause.Occur.MUST);
|
||||
|
||||
// This part does the scoring
|
||||
Query spatialRankingQuery = new FunctionQuery(valueSource);
|
||||
bq.add(spatialRankingQuery, BooleanClause.Occur.MUST);
|
||||
return bq;
|
||||
}
|
||||
|
||||
// Utility on SpatialStrategy?
|
||||
// public Query makeQueryWithValueSource(SpatialArgs args, ValueSource valueSource) {
|
||||
// return new FilteredQuery(new FunctionQuery(valueSource), makeFilter(args));
|
||||
// }
|
||||
|
||||
private Query makeSpatialQuery(SpatialArgs args) {
|
||||
Shape shape = args.getShape();
|
||||
|
@ -192,11 +247,6 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
return spatial;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
//
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a query to retrieve documents that fully contain the input envelope.
|
||||
*
|
||||
|
@ -209,8 +259,8 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
|
||||
// Y conditions
|
||||
// docMinY <= queryExtent.getMinY() AND docMaxY >= queryExtent.getMaxY()
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, precisionStep, null, bbox.getMinY(), false, true);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, precisionStep, bbox.getMaxY(), null, true, false);
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, getPrecisionStep(), null, bbox.getMinY(), false, true);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, getPrecisionStep(), bbox.getMaxY(), null, true, false);
|
||||
Query yConditions = this.makeQuery(BooleanClause.Occur.MUST, qMinY, qMaxY);
|
||||
|
||||
// X conditions
|
||||
|
@ -222,25 +272,35 @@ 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 = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, null, bbox.getMinX(), false, true);
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, bbox.getMaxX(), null, true, false);
|
||||
Query qMinX = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), null, bbox.getMinX(), false, true);
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), bbox.getMaxX(), null, true, false);
|
||||
Query qMinMax = this.makeQuery(BooleanClause.Occur.MUST, qMinX, qMaxX);
|
||||
Query qNonXDL = this.makeXDL(false, qMinMax);
|
||||
|
||||
// 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(field_minX, precisionStep, null, bbox.getMinX(), false, true);
|
||||
Query qXDLRight = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, bbox.getMaxX(), null, true, false);
|
||||
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.SHOULD, qXDLLeft, qXDLRight);
|
||||
Query qXDL = this.makeXDL(true, qXDLLeftRight);
|
||||
if (!ctx.isGeo()) {
|
||||
xConditions = qNonXDL;
|
||||
} else {
|
||||
// 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(field_minX, getPrecisionStep(), null, bbox.getMinX(), false, true);
|
||||
Query qXDLRight = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), bbox.getMaxX(), null, true, false);
|
||||
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.SHOULD, qXDLLeft, qXDLRight);
|
||||
Query qXDL = this.makeXDL(true, qXDLLeftRight);
|
||||
|
||||
// apply the non-XDL and XDL conditions
|
||||
xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL);
|
||||
Query qEdgeDL = null;
|
||||
if (bbox.getMinX() == bbox.getMaxX() && Math.abs(bbox.getMinX()) == 180) {
|
||||
double edge = bbox.getMinX() * -1;//opposite dateline edge
|
||||
qEdgeDL = makeQuery(BooleanClause.Occur.SHOULD,
|
||||
makeNumberTermQuery(field_minX, edge), makeNumberTermQuery(field_maxX, edge));
|
||||
}
|
||||
|
||||
// queries that cross the date line
|
||||
// apply the non-XDL and XDL conditions
|
||||
xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL, qEdgeDL);
|
||||
}
|
||||
} else {
|
||||
// queries that cross the date line
|
||||
|
||||
// No need to search for documents that do not cross the date line
|
||||
|
||||
|
@ -248,11 +308,14 @@ 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 = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, null, bbox.getMinX(), false, true);
|
||||
Query qXDLRight = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, bbox.getMaxX(), null, true, false);
|
||||
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qXDLLeft, qXDLRight);
|
||||
Query qXDLLeft = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), null, bbox.getMinX(), false, true);
|
||||
Query qXDLRight = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), bbox.getMaxX(), null, true, false);
|
||||
Query qXDLLeftRight = this.makeXDL(true, this.makeQuery(BooleanClause.Occur.MUST, qXDLLeft, qXDLRight));
|
||||
|
||||
xConditions = this.makeXDL(true, qXDLLeftRight);
|
||||
Query qWorld = makeQuery(BooleanClause.Occur.MUST,
|
||||
makeNumberTermQuery(field_minX, -180), makeNumberTermQuery(field_maxX, 180));
|
||||
|
||||
xConditions = makeQuery(BooleanClause.Occur.SHOULD, qXDLLeftRight, qWorld);
|
||||
}
|
||||
|
||||
// both X and Y conditions must occur
|
||||
|
@ -271,8 +334,8 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
|
||||
// Y conditions
|
||||
// docMinY > queryExtent.getMaxY() OR docMaxY < queryExtent.getMinY()
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, precisionStep, bbox.getMaxY(), null, false, false);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, precisionStep, null, bbox.getMinY(), false, false);
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, getPrecisionStep(), bbox.getMaxY(), null, false, false);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, getPrecisionStep(), null, bbox.getMinY(), false, false);
|
||||
Query yConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qMinY, qMaxY);
|
||||
|
||||
// X conditions
|
||||
|
@ -283,26 +346,42 @@ 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 = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, bbox.getMaxX(), null, false, false);
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, bbox.getMinX(), false, false);
|
||||
Query qMinX = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMaxX(), null, false, false);
|
||||
if (bbox.getMinX() == -180.0 && ctx.isGeo()) {//touches dateline; -180 == 180
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(qMinX, BooleanClause.Occur.MUST);
|
||||
bq.add(makeNumberTermQuery(field_maxX, 180.0), BooleanClause.Occur.MUST_NOT);
|
||||
qMinX = bq;
|
||||
}
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMinX(), false, false);
|
||||
if (bbox.getMaxX() == 180.0 && ctx.isGeo()) {//touches dateline; -180 == 180
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(qMaxX, BooleanClause.Occur.MUST);
|
||||
bq.add(makeNumberTermQuery(field_minX, -180.0), BooleanClause.Occur.MUST_NOT);
|
||||
qMaxX = bq;
|
||||
}
|
||||
Query qMinMax = this.makeQuery(BooleanClause.Occur.SHOULD, qMinX, qMaxX);
|
||||
Query qNonXDL = this.makeXDL(false, qMinMax);
|
||||
|
||||
// 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(field_minX, precisionStep, bbox.getMaxX(), null, false, false);
|
||||
Query qMaxXRight = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, bbox.getMinX(), false, false);
|
||||
Query qLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qMinXLeft, qMaxXRight);
|
||||
Query qXDL = this.makeXDL(true, qLeftRight);
|
||||
if (!ctx.isGeo()) {
|
||||
xConditions = qNonXDL;
|
||||
} else {
|
||||
// X Conditions for documents that cross the date line,
|
||||
|
||||
// apply the non-XDL and XDL conditions
|
||||
xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL);
|
||||
// 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(field_minX, getPrecisionStep(), bbox.getMaxX(), null, false, false);
|
||||
Query qMaxXRight = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMinX(), false, false);
|
||||
Query qLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qMinXLeft, qMaxXRight);
|
||||
Query qXDL = this.makeXDL(true, qLeftRight);
|
||||
|
||||
// apply the non-XDL and XDL conditions
|
||||
xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL);
|
||||
}
|
||||
// queries that cross the date line
|
||||
} else {
|
||||
|
||||
|
@ -310,10 +389,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 = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, 180.0, null, false, false);
|
||||
Query qMaxXLeft = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, bbox.getMinX(), false, false);
|
||||
Query qMinXRight = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, bbox.getMaxX(), null, false, false);
|
||||
Query qMaxXRight = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, -180.0, false, false);
|
||||
Query qMinXLeft = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), 180.0, null, false, false);
|
||||
Query qMaxXLeft = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMinX(), false, false);
|
||||
Query qMinXRight = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMaxX(), null, false, false);
|
||||
Query qMaxXRight = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), 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);
|
||||
|
@ -335,16 +414,11 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
Query makeEquals(Rectangle bbox) {
|
||||
|
||||
// docMinX = queryExtent.getMinX() AND docMinY = queryExtent.getMinY() AND docMaxX = queryExtent.getMaxX() AND docMaxY = queryExtent.getMaxY()
|
||||
Query qMinX = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, bbox.getMinX(), bbox.getMinX(), true, true);
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, precisionStep, bbox.getMinY(), bbox.getMinY(), true, true);
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, bbox.getMaxX(), bbox.getMaxX(), true, true);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, 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;
|
||||
Query qMinX = makeNumberTermQuery(field_minX, bbox.getMinX());
|
||||
Query qMinY = makeNumberTermQuery(field_minY, bbox.getMinY());
|
||||
Query qMaxX = makeNumberTermQuery(field_maxX, bbox.getMaxX());
|
||||
Query qMaxY = makeNumberTermQuery(field_maxY, bbox.getMaxY());
|
||||
return makeQuery(BooleanClause.Occur.MUST, qMinX, qMinY, qMaxX, qMaxY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -361,12 +435,18 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
// to get around it we add all documents as a SHOULD
|
||||
|
||||
// there must be an envelope, it must not be disjoint
|
||||
Query qDisjoint = makeDisjoint(bbox);
|
||||
Query qIsNonXDL = this.makeXDL(false);
|
||||
Query qIsXDL = this.makeXDL(true);
|
||||
Query qHasEnv = this.makeQuery(BooleanClause.Occur.SHOULD, qIsNonXDL, qIsXDL);
|
||||
Query qHasEnv;
|
||||
if (ctx.isGeo()) {
|
||||
Query qIsNonXDL = this.makeXDL(false);
|
||||
Query qIsXDL = ctx.isGeo() ? this.makeXDL(true) : null;
|
||||
qHasEnv = this.makeQuery(BooleanClause.Occur.SHOULD, qIsNonXDL, qIsXDL);
|
||||
} else {
|
||||
qHasEnv = this.makeXDL(false);
|
||||
}
|
||||
|
||||
BooleanQuery qNotDisjoint = new BooleanQuery();
|
||||
qNotDisjoint.add(qHasEnv, BooleanClause.Occur.MUST);
|
||||
Query qDisjoint = makeDisjoint(bbox);
|
||||
qNotDisjoint.add(qDisjoint, BooleanClause.Occur.MUST_NOT);
|
||||
|
||||
//Query qDisjoint = makeDisjoint();
|
||||
|
@ -386,7 +466,8 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
BooleanQuery makeQuery(BooleanClause.Occur occur, Query... queries) {
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
for (Query query : queries) {
|
||||
bq.add(query, occur);
|
||||
if (query != null)
|
||||
bq.add(query, occur);
|
||||
}
|
||||
return bq;
|
||||
}
|
||||
|
@ -403,40 +484,38 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
|
||||
// Y conditions
|
||||
// docMinY >= queryExtent.getMinY() AND docMaxY <= queryExtent.getMaxY()
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, precisionStep, bbox.getMinY(), null, true, false);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, precisionStep, null, bbox.getMaxY(), false, true);
|
||||
Query qMinY = NumericRangeQuery.newDoubleRange(field_minY, getPrecisionStep(), bbox.getMinY(), null, true, false);
|
||||
Query qMaxY = NumericRangeQuery.newDoubleRange(field_maxY, getPrecisionStep(), null, bbox.getMaxY(), false, true);
|
||||
Query yConditions = this.makeQuery(BooleanClause.Occur.MUST, qMinY, qMaxY);
|
||||
|
||||
// X conditions
|
||||
Query xConditions;
|
||||
|
||||
// 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(field_minX, precisionStep, bbox.getMinX(), null, true, false);
|
||||
Query qXDLRight = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, bbox.getMaxX(), false, true);
|
||||
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qXDLLeft, qXDLRight);
|
||||
Query qXDL = this.makeXDL(true, qXDLLeftRight);
|
||||
if (ctx.isGeo() && bbox.getMinX() == -180.0 && bbox.getMaxX() == 180.0) {
|
||||
//if query world-wraps, only the y condition matters
|
||||
return yConditions;
|
||||
|
||||
// queries that do not cross the date line
|
||||
if (!bbox.getCrossesDateLine()) {
|
||||
} else if (!bbox.getCrossesDateLine()) {
|
||||
// queries that do not cross the date line
|
||||
|
||||
// X Conditions for documents that do not cross the date line,
|
||||
// docMinX >= queryExtent.getMinX() AND docMaxX <= queryExtent.getMaxX()
|
||||
Query qMinX = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, bbox.getMinX(), null, true, false);
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, bbox.getMaxX(), false, true);
|
||||
Query qMinX = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMinX(), null, true, false);
|
||||
Query qMaxX = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMaxX(), false, true);
|
||||
Query qMinMax = this.makeQuery(BooleanClause.Occur.MUST, qMinX, qMaxX);
|
||||
Query qNonXDL = this.makeXDL(false, qMinMax);
|
||||
|
||||
// apply the non-XDL or XDL X conditions
|
||||
if ((bbox.getMinX() <= -180.0) && bbox.getMaxX() >= 180.0) {
|
||||
xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL);
|
||||
} else {
|
||||
xConditions = qNonXDL;
|
||||
double edge = 0;//none, otherwise opposite dateline of query
|
||||
if (bbox.getMinX() == -180.0)
|
||||
edge = 180;
|
||||
else if (bbox.getMaxX() == 180.0)
|
||||
edge = -180;
|
||||
if (edge != 0 && ctx.isGeo()) {
|
||||
Query edgeQ = makeQuery(BooleanClause.Occur.MUST,
|
||||
makeNumberTermQuery(field_minX, edge), makeNumberTermQuery(field_maxX, edge));
|
||||
qMinMax = makeQuery(BooleanClause.Occur.SHOULD, qMinMax, edgeQ);
|
||||
}
|
||||
|
||||
xConditions = this.makeXDL(false, qMinMax);
|
||||
|
||||
// queries that cross the date line
|
||||
} else {
|
||||
|
||||
|
@ -444,14 +523,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 = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, bbox.getMinX(), null, true, false);
|
||||
Query qMaxXLeft = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, 180.0, false, true);
|
||||
Query qMinXLeft = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), bbox.getMinX(), null, true, false);
|
||||
Query qMaxXLeft = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), 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 = NumericRangeQuery.newDoubleRange(field_minX, precisionStep, -180.0, null, true, false);
|
||||
Query qMaxXRight = NumericRangeQuery.newDoubleRange(field_maxX, precisionStep, null, bbox.getMaxX(), false, true);
|
||||
Query qMinXRight = NumericRangeQuery.newDoubleRange(field_minX, getPrecisionStep(), -180.0, null, true, false);
|
||||
Query qMaxXRight = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMaxX(), false, true);
|
||||
Query qRight = this.makeQuery(BooleanClause.Occur.MUST, qMinXRight, qMaxXRight);
|
||||
|
||||
// either left or right conditions should occur,
|
||||
|
@ -459,6 +538,16 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
Query qLeftRight = this.makeQuery(BooleanClause.Occur.SHOULD, qLeft, qRight);
|
||||
Query qNonXDL = this.makeXDL(false, qLeftRight);
|
||||
|
||||
// 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(field_minX, getPrecisionStep(), bbox.getMinX(), null, true, false);
|
||||
Query qXDLRight = NumericRangeQuery.newDoubleRange(field_maxX, getPrecisionStep(), null, bbox.getMaxX(), false, true);
|
||||
Query qXDLLeftRight = this.makeQuery(BooleanClause.Occur.MUST, qXDLLeft, qXDLRight);
|
||||
Query qXDL = this.makeXDL(true, qXDLLeftRight);
|
||||
|
||||
// apply the non-XDL and XDL conditions
|
||||
xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL);
|
||||
}
|
||||
|
@ -470,11 +559,10 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
/**
|
||||
* 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) {
|
||||
private Query makeXDL(boolean crossedDateLine) {
|
||||
// The 'T' and 'F' values match solr fields
|
||||
return new TermQuery(new Term(field_xdl, crossedDateLine ? "T" : "F"));
|
||||
}
|
||||
|
@ -487,12 +575,23 @@ public class BBoxStrategy extends SpatialStrategy {
|
|||
* @param query the spatial query
|
||||
* @return the query
|
||||
*/
|
||||
Query makeXDL(boolean crossedDateLine, Query query) {
|
||||
private Query makeXDL(boolean crossedDateLine, Query query) {
|
||||
if (!ctx.isGeo()) {
|
||||
assert !crossedDateLine;
|
||||
return query;
|
||||
}
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(this.makeXDL(crossedDateLine), BooleanClause.Occur.MUST);
|
||||
bq.add(query, BooleanClause.Occur.MUST);
|
||||
return bq;
|
||||
}
|
||||
|
||||
private Query makeNumberTermQuery(String field, double number) {
|
||||
BytesRef bytes = new BytesRef();
|
||||
NumericUtils.longToPrefixCodedBytes(NumericUtils.doubleToSortableLong(number), 0, bytes);
|
||||
return new TermQuery(new Term(field, bytes));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
package org.apache.lucene.spatial.bbox;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import com.spatial4j.core.shape.Rectangle;
|
||||
import org.apache.lucene.index.AtomicReader;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.index.DocValues;
|
||||
import org.apache.lucene.index.NumericDocValues;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.util.Bits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A ValueSource in which the indexed Rectangle is returned from
|
||||
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
|
||||
*
|
||||
* @lucene.internal
|
||||
*/
|
||||
class BBoxValueSource extends ValueSource {
|
||||
|
||||
private final BBoxStrategy strategy;
|
||||
|
||||
public BBoxValueSource(BBoxStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "bboxShape(" + strategy.getFieldName() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
AtomicReader reader = readerContext.reader();
|
||||
final NumericDocValues minX = DocValues.getNumeric(reader, strategy.field_minX);
|
||||
final NumericDocValues minY = DocValues.getNumeric(reader, strategy.field_minY);
|
||||
final NumericDocValues maxX = DocValues.getNumeric(reader, strategy.field_maxX);
|
||||
final NumericDocValues maxY = DocValues.getNumeric(reader, strategy.field_maxY);
|
||||
|
||||
final Bits validBits = DocValues.getDocsWithField(reader, strategy.field_minX);//could have chosen any field
|
||||
//reused
|
||||
final Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
|
||||
|
||||
return new FunctionValues() {
|
||||
@Override
|
||||
public Object objectVal(int doc) {
|
||||
if (!validBits.get(doc)) {
|
||||
return null;
|
||||
} else {
|
||||
rect.reset(
|
||||
Double.longBitsToDouble(minX.get(doc)), Double.longBitsToDouble(maxX.get(doc)),
|
||||
Double.longBitsToDouble(minY.get(doc)), Double.longBitsToDouble(maxY.get(doc)));
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String strVal(int doc) {//TODO support WKT output once Spatial4j does
|
||||
Object v = objectVal(doc);
|
||||
return v == null ? null : v.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int doc) {
|
||||
return validBits.get(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(int doc) {
|
||||
return new Explanation(Float.NaN, toString(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int doc) {
|
||||
return description() + '=' + strVal(doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
BBoxValueSource that = (BBoxValueSource) o;
|
||||
|
||||
if (!strategy.equals(that.strategy)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return strategy.hashCode();
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package org.apache.lucene.spatial.bbox;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import com.spatial4j.core.context.SpatialContext;
|
||||
import com.spatial4j.core.distance.DistanceCalculator;
|
||||
import com.spatial4j.core.shape.Point;
|
||||
import com.spatial4j.core.shape.Rectangle;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
|
||||
/**
|
||||
* Returns the distance between the center of the indexed rectangle and the
|
||||
* query shape.
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class DistanceSimilarity implements BBoxSimilarity {
|
||||
private final Point queryPoint;
|
||||
private final double multiplier;
|
||||
private final DistanceCalculator distCalc;
|
||||
private final double nullValue;
|
||||
|
||||
public DistanceSimilarity(SpatialContext ctx, Point queryPoint, double multiplier) {
|
||||
this.queryPoint = queryPoint;
|
||||
this.multiplier = multiplier;
|
||||
this.distCalc = ctx.getDistCalc();
|
||||
this.nullValue = (ctx.isGeo() ? 180 * multiplier : Double.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double score(Rectangle indexRect, Explanation exp) {
|
||||
double score;
|
||||
if (indexRect == null) {
|
||||
score = nullValue;
|
||||
} else {
|
||||
score = distCalc.distance(queryPoint, indexRect.getCenter()) * multiplier;
|
||||
}
|
||||
if (exp != null) {
|
||||
exp.setValue((float)score);
|
||||
exp.setDescription(this.getClass().getSimpleName());
|
||||
exp.addDetail(new Explanation(-1f, "" + queryPoint));
|
||||
exp.addDetail(new Explanation(-1f,""+indexRect));
|
||||
exp.addDetail(new Explanation((float)multiplier,"multiplier"));
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
DistanceSimilarity that = (DistanceSimilarity) o;
|
||||
|
||||
if (Double.compare(that.multiplier, multiplier) != 0) return false;
|
||||
if (Double.compare(that.nullValue, nullValue) != 0) return false;
|
||||
if (!distCalc.equals(that.distCalc)) return false;
|
||||
if (!queryPoint.equals(that.queryPoint)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result;
|
||||
long temp;
|
||||
result = queryPoint.hashCode();
|
||||
temp = Double.doubleToLongBits(multiplier);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
result = 31 * result + distCalc.hashCode();
|
||||
temp = Double.doubleToLongBits(nullValue);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package org.apache.lucene.spatial.util;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import com.spatial4j.core.context.SpatialContext;
|
||||
import com.spatial4j.core.shape.Shape;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The area of a Shape retrieved from a ValueSource via
|
||||
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
|
||||
*
|
||||
* @see Shape#getArea(com.spatial4j.core.context.SpatialContext)
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public class ShapeAreaValueSource extends ValueSource {
|
||||
private final ValueSource shapeValueSource;
|
||||
private final SpatialContext ctx;//not part of identity; should be associated with shapeValueSource indirectly
|
||||
private final boolean geoArea;
|
||||
|
||||
public ShapeAreaValueSource(ValueSource shapeValueSource, SpatialContext ctx, boolean geoArea) {
|
||||
this.shapeValueSource = shapeValueSource;
|
||||
this.ctx = ctx;
|
||||
this.geoArea = geoArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "area(" + shapeValueSource.description() + ",geo=" + geoArea + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWeight(Map context, IndexSearcher searcher) throws IOException {
|
||||
shapeValueSource.createWeight(context, searcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
final FunctionValues shapeValues = shapeValueSource.getValues(context, readerContext);
|
||||
|
||||
return new DoubleDocValues(this) {
|
||||
@Override
|
||||
public double doubleVal(int doc) {
|
||||
Shape shape = (Shape) shapeValues.objectVal(doc);
|
||||
if (shape == null || shape.isEmpty())
|
||||
return 0;//or NaN?
|
||||
//This part of Spatial4j API is kinda weird. Passing null means 2D area, otherwise geo
|
||||
// assuming ctx.isGeo()
|
||||
return shape.getArea( geoArea ? ctx : null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int doc) {
|
||||
return shapeValues.exists(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(int doc) {
|
||||
Explanation exp = super.explain(doc);
|
||||
exp.addDetail(shapeValues.explain(doc));
|
||||
return exp;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ShapeAreaValueSource that = (ShapeAreaValueSource) o;
|
||||
|
||||
if (geoArea != that.geoArea) return false;
|
||||
if (!shapeValueSource.equals(that.shapeValueSource)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = shapeValueSource.hashCode();
|
||||
result = 31 * result + (geoArea ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,7 @@ import com.spatial4j.core.context.SpatialContext;
|
|||
import com.spatial4j.core.shape.Point;
|
||||
import com.spatial4j.core.shape.Shape;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.spatial.bbox.BBoxStrategy;
|
||||
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
||||
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
||||
|
@ -90,9 +91,20 @@ 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.setIndexed(false);
|
||||
bboxStrategy.setFieldType(fieldType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean needsDocValues() {
|
||||
return (strategy instanceof SerializedDVStrategy);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -70,10 +70,6 @@ public abstract class SpatialTestCase extends LuceneTestCase {
|
|||
super.setUp();
|
||||
// TODO: change this module to index docvalues instead of uninverting
|
||||
uninvertMap.clear();
|
||||
uninvertMap.put("bbox__minX", Type.DOUBLE);
|
||||
uninvertMap.put("bbox__maxX", Type.DOUBLE);
|
||||
uninvertMap.put("bbox__minY", Type.DOUBLE);
|
||||
uninvertMap.put("bbox__maxY", Type.DOUBLE);
|
||||
uninvertMap.put("pointvector__x", Type.DOUBLE);
|
||||
uninvertMap.put("pointvector__y", Type.DOUBLE);
|
||||
|
||||
|
|
|
@ -17,52 +17,235 @@ package org.apache.lucene.spatial.bbox;
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
||||
import com.spatial4j.core.context.SpatialContext;
|
||||
import com.spatial4j.core.context.SpatialContextFactory;
|
||||
import com.spatial4j.core.distance.DistanceUtils;
|
||||
import com.spatial4j.core.shape.Rectangle;
|
||||
import com.spatial4j.core.shape.Shape;
|
||||
import com.spatial4j.core.shape.impl.RectangleImpl;
|
||||
import org.apache.lucene.spatial.SpatialMatchConcern;
|
||||
import org.apache.lucene.spatial.StrategyTestCase;
|
||||
import org.junit.Before;
|
||||
import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase;
|
||||
import org.apache.lucene.spatial.query.SpatialOperation;
|
||||
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class TestBBoxStrategy extends StrategyTestCase {
|
||||
public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
protected Shape randomIndexedShape() {
|
||||
Rectangle world = ctx.getWorldBounds();
|
||||
if (random().nextInt(10) == 0) // increased chance of getting one of these
|
||||
return world;
|
||||
|
||||
int worldWidth = (int) Math.round(world.getWidth());
|
||||
int deltaLeft = nextIntInclusive(worldWidth);
|
||||
int deltaRight = nextIntInclusive(worldWidth - deltaLeft);
|
||||
int worldHeight = (int) Math.round(world.getHeight());
|
||||
int deltaTop = nextIntInclusive(worldHeight);
|
||||
int deltaBottom = nextIntInclusive(worldHeight - deltaTop);
|
||||
if (ctx.isGeo() && (deltaLeft != 0 || deltaRight != 0)) {
|
||||
//if geo & doesn't world-wrap, we shift randomly to potentially cross dateline
|
||||
int shift = nextIntInclusive(360);
|
||||
return ctx.makeRectangle(
|
||||
DistanceUtils.normLonDEG(world.getMinX() + deltaLeft + shift),
|
||||
DistanceUtils.normLonDEG(world.getMaxX() - deltaRight + shift),
|
||||
world.getMinY() + deltaBottom, world.getMaxY() - deltaTop);
|
||||
} else {
|
||||
return ctx.makeRectangle(
|
||||
world.getMinX() + deltaLeft, world.getMaxX() - deltaRight,
|
||||
world.getMinY() + deltaBottom, world.getMaxY() - deltaTop);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** next int, inclusive, rounds to multiple of 10 if given evenly divisible. */
|
||||
private int nextIntInclusive(int toInc) {
|
||||
final int DIVIS = 10;
|
||||
if (toInc % DIVIS == 0) {
|
||||
return random().nextInt(toInc/DIVIS + 1) * DIVIS;
|
||||
} else {
|
||||
return random().nextInt(toInc + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Shape randomQueryShape() {
|
||||
return randomIndexedShape();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Repeat(iterations = 20)
|
||||
public void testOperations() throws IOException {
|
||||
//setup
|
||||
if (random().nextInt(4) > 0) {//75% of the time choose geo (more interesting to test)
|
||||
this.ctx = SpatialContext.GEO;
|
||||
} else {
|
||||
SpatialContextFactory factory = new SpatialContextFactory();
|
||||
factory.geo = false;
|
||||
factory.worldBounds = new RectangleImpl(-300, 300, -100, 100, null);
|
||||
this.ctx = factory.newSpatialContext();
|
||||
}
|
||||
this.strategy = new BBoxStrategy(ctx, "bbox");
|
||||
|
||||
for (SpatialOperation operation : SpatialOperation.values()) {
|
||||
if (operation == SpatialOperation.Overlaps)
|
||||
continue;//unsupported
|
||||
testOperationRandomShapes(operation);
|
||||
|
||||
deleteAll();
|
||||
commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntersectsBugDatelineEdge() throws IOException {
|
||||
setupGeo();
|
||||
testOperation(
|
||||
ctx.makeRectangle(160, 180, -10, 10),
|
||||
SpatialOperation.Intersects,
|
||||
ctx.makeRectangle(-180, -160, -10, 10), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntersectsWorldDatelineEdge() throws IOException {
|
||||
setupGeo();
|
||||
testOperation(
|
||||
ctx.makeRectangle(-180, 180, -10, 10),
|
||||
SpatialOperation.Intersects,
|
||||
ctx.makeRectangle(180, 180, -10, 10), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithinBugDatelineEdge() throws IOException {
|
||||
setupGeo();
|
||||
testOperation(
|
||||
ctx.makeRectangle(180, 180, -10, 10),
|
||||
SpatialOperation.IsWithin,
|
||||
ctx.makeRectangle(-180, -100, -10, 10), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsBugDatelineEdge() throws IOException {
|
||||
setupGeo();
|
||||
testOperation(
|
||||
ctx.makeRectangle(-180, -150, -10, 10),
|
||||
SpatialOperation.Contains,
|
||||
ctx.makeRectangle(180, 180, -10, 10), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorldContainsXDL() throws IOException {
|
||||
setupGeo();
|
||||
testOperation(
|
||||
ctx.makeRectangle(-180, 180, -10, 10),
|
||||
SpatialOperation.Contains,
|
||||
ctx.makeRectangle(170, -170, -10, 10), true);
|
||||
}
|
||||
|
||||
private void setupGeo() {
|
||||
this.ctx = SpatialContext.GEO;
|
||||
this.strategy = new BBoxStrategy(ctx, "bbox");
|
||||
}
|
||||
|
||||
// OLD STATIC TESTS (worthless?)
|
||||
|
||||
@Test @Ignore("Overlaps not supported")
|
||||
public void testBasicOperaions() throws IOException {
|
||||
setupGeo();
|
||||
getAddAndVerifyIndexedDocuments(DATA_SIMPLE_BBOX);
|
||||
|
||||
executeQueries(SpatialMatchConcern.EXACT, QTEST_Simple_Queries_BBox);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatesBBox() throws IOException {
|
||||
setupGeo();
|
||||
getAddAndVerifyIndexedDocuments(DATA_STATES_BBOX);
|
||||
|
||||
executeQueries(SpatialMatchConcern.FILTER, QTEST_States_IsWithin_BBox);
|
||||
executeQueries(SpatialMatchConcern.FILTER, QTEST_States_Intersects_BBox);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCitiesIntersectsBBox() throws IOException {
|
||||
setupGeo();
|
||||
getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
|
||||
|
||||
executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_Intersects_BBox);
|
||||
}
|
||||
|
||||
/* Convert DATA_WORLD_CITIES_POINTS to bbox */
|
||||
@Override
|
||||
protected Shape convertShapeFromGetDocuments(Shape shape) {
|
||||
return shape.getBoundingBox();
|
||||
}
|
||||
|
||||
@Test @Ignore("Overlaps not supported")
|
||||
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);
|
||||
public void testOverlapRatio() throws IOException {
|
||||
setupGeo();
|
||||
|
||||
//Simply assert null shape results in 0
|
||||
adoc("999", (Shape) null);
|
||||
commit();
|
||||
BBoxStrategy bboxStrategy = (BBoxStrategy) strategy;
|
||||
checkValueSource(bboxStrategy.makeOverlapRatioValueSource(randomRectangle(), 0.0), new float[]{0f}, 0f);
|
||||
|
||||
//we test raw BBoxOverlapRatioValueSource without actual indexing
|
||||
for (int SHIFT = 0; SHIFT < 360; SHIFT += 10) {
|
||||
Rectangle queryBox = shiftedRect(0, 40, -20, 20, SHIFT);//40x40, 1600 area
|
||||
|
||||
final boolean MSL = random().nextBoolean();
|
||||
final double minSideLength = MSL ? 0.1 : 0.0;
|
||||
BBoxOverlapRatioValueSource sim = new BBoxOverlapRatioValueSource(null, true, queryBox, 0.5, minSideLength);
|
||||
int nudge = SHIFT == 0 ? 0 : random().nextInt(3) * 10 - 10;//-10, 0, or 10. Keep 0 on first round.
|
||||
|
||||
final double EPS = 0.0000001;
|
||||
|
||||
assertEquals("within", (200d/1600d * 0.5) + (0.5), sim.score(shiftedRect(10, 30, 0, 10, SHIFT + nudge), null), EPS);
|
||||
|
||||
assertEquals("in25%", 0.25, sim.score(shiftedRect(30, 70, -20, 20, SHIFT), null), EPS);
|
||||
|
||||
assertEquals("wrap", 0.2794117, sim.score(shiftedRect(30, 10, -20, 20, SHIFT + nudge), null), EPS);
|
||||
|
||||
assertEquals("no intersection H", 0.0, sim.score(shiftedRect(-10, -10, -20, 20, SHIFT), null), EPS);
|
||||
assertEquals("no intersection V", 0.0, sim.score(shiftedRect(0, 20, -30, -30, SHIFT), null), EPS);
|
||||
|
||||
assertEquals("point", 0.5 + (MSL?(0.1*0.1/1600.0/2.0):0), sim.score(shiftedRect(0, 0, 0, 0, SHIFT), null), EPS);
|
||||
|
||||
assertEquals("line 25% intersection", 0.25/2 + (MSL?(10.0*0.1/1600.0/2.0):0.0), sim.score(shiftedRect(-30, 10, 0, 0, SHIFT), null), EPS);
|
||||
|
||||
//test with point query
|
||||
sim = new BBoxOverlapRatioValueSource(null, true, shiftedRect(0, 0, 0, 0, SHIFT), 0.5, minSideLength);
|
||||
assertEquals("same", 1.0, sim.score(shiftedRect(0, 0, 0, 0, SHIFT), null), EPS);
|
||||
assertEquals("contains", 0.5 + (MSL?(0.1*0.1/(30*10)/2.0):0.0), sim.score(shiftedRect(0, 30, 0, 10, SHIFT), null), EPS);
|
||||
|
||||
//test with line query (vertical this time)
|
||||
sim = new BBoxOverlapRatioValueSource(null, true, shiftedRect(0, 0, 20, 40, SHIFT), 0.5, minSideLength);
|
||||
assertEquals("line 50%", 0.5, sim.score(shiftedRect(0, 0, 10, 30, SHIFT), null), EPS);
|
||||
assertEquals("point", 0.5 + (MSL?(0.1*0.1/(20*0.1)/2.0):0.0), sim.score(shiftedRect(0, 0, 30, 30, SHIFT), null), EPS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCitiesIntersectsBBox() throws IOException {
|
||||
getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
|
||||
|
||||
executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_Intersects_BBox);
|
||||
private Rectangle shiftedRect(double minX, double maxX, double minY, double maxY, int xShift) {
|
||||
return ctx.makeRectangle(
|
||||
DistanceUtils.normLonDEG(minX + xShift),
|
||||
DistanceUtils.normLonDEG(maxX + xShift),
|
||||
minY, maxY);
|
||||
}
|
||||
|
||||
public void testAreaValueSource() throws IOException {
|
||||
setupGeo();
|
||||
adoc("100", ctx.makeRectangle(0, 20, 40, 80));
|
||||
adoc("999", (Shape) null);
|
||||
commit();
|
||||
BBoxStrategy bboxStrategy = (BBoxStrategy) strategy;
|
||||
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, false),
|
||||
new float[]{800f, 0f}, 0f);
|
||||
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, true),//geo
|
||||
new float[]{391.93f, 0f}, 0.01f);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue