You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2014/07/08 16:15:36 UTC

svn commit: r1608793 - in /lucene/dev/trunk/lucene: ./ spatial/src/java/org/apache/lucene/spatial/bbox/ spatial/src/java/org/apache/lucene/spatial/util/ spatial/src/test/org/apache/lucene/spatial/ spatial/src/test/org/apache/lucene/spatial/bbox/

Author: dsmiley
Date: Tue Jul  8 14:15:35 2014
New Revision: 1608793

URL: http://svn.apache.org/r1608793
Log:
LUCENE-5714, LUCENE-5779: Enhance BBoxStrategy & Overlap similarity. Configurable docValues / index usage.
Add new ShapeAreaValueSource.

Added:
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxOverlapRatioValueSource.java
      - copied, changed from r1608751, lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/AreaSimilarity.java
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxValueSource.java   (with props)
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeAreaValueSource.java   (with props)
Removed:
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/AreaSimilarity.java
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarity.java
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/DistanceSimilarity.java
Modified:
    lucene/dev/trunk/lucene/CHANGES.txt
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java
    lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java
    lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
    lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java
    lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java

Modified: lucene/dev/trunk/lucene/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/CHANGES.txt?rev=1608793&r1=1608792&r2=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/CHANGES.txt (original)
+++ lucene/dev/trunk/lucene/CHANGES.txt Tue Jul  8 14:15:35 2014
@@ -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
 

Copied: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxOverlapRatioValueSource.java (from r1608751, lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/AreaSimilarity.java)
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxOverlapRatioValueSource.java?p2=lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxOverlapRatioValueSource.java&p1=lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/AreaSimilarity.java&r1=1608751&r2=1608793&rev=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/AreaSimilarity.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxOverlapRatioValueSource.java Tue Jul  8 14:15:35 2014
@@ -16,202 +16,229 @@
  */
 package org.apache.lucene.spatial.bbox;
 
-import org.apache.lucene.search.Explanation;
-
 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 overlays rather than
+ * 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:
- * <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
+ * <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>.
+ *   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 AreaSimilarity implements BBoxSimilarity {
-  /**
-   * Properties associated with the query envelope
-   */
+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;
+  private final double queryArea;//not part of identity
 
-  private final double targetPower;
-  private final double queryPower;
+  private final double minSideLength;
 
-  public AreaSimilarity(Rectangle queryExtent, double queryPower, double targetPower) {
-    this.queryExtent = queryExtent;
-    this.queryArea = queryExtent.getArea(null);
+  private final double queryTargetProportion;
 
-    this.queryPower = queryPower;
-    this.targetPower = targetPower;
+  //TODO option to compute geodetic area
 
-//  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);
-//  }
+  /**
+   *
+   * @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");
   }
 
-  public AreaSimilarity(Rectangle queryExtent) {
-    this(queryExtent, 2.0, 0.5);
+  /** 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;
 
-  public String getDelimiterQueryParameters() {
-    return queryExtent.toString() + ";" + queryPower + ";" + targetPower;
+    return true;
   }
 
   @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;
+  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;
-    double width = 0;
+    if (height < 0)
+      return 0;//no intersection
 
-    // 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;
+    // 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 {
-        double left = Math.max(queryExtent.getMinX(), target.getMinX());
-        double right = Math.min(queryExtent.getMaxX(), target.getMaxX());
-        width = right - left;
+        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 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"));
-      }
+    // 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
     }
-    else if(exp !=null) {
-      exp.setValue(0);
-      exp.setDescription("Shape does not intersect");
+
+    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
     }
-    return score;
-  }
+    assert queryRatio >= 0 && queryRatio <= 1 : queryRatio;
+    assert targetRatio >= 0 && targetRatio <= 1 : targetRatio;
 
+    // combine ratios into a 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;
+    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));
+    }
 
-    AreaSimilarity other = (AreaSimilarity) o;
-    return getDelimiterQueryParameters().equals(other.getDelimiterQueryParameters());
+    return score;
   }
 
-  /**
-   * Returns the ValueSource hash code.
-   *
-   * @return the hash code
-   */
-  @Override
-  public int hashCode() {
-    return getDelimiterQueryParameters().hashCode();
+  /** 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);
   }
+
 }

Modified: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java?rev=1608793&r1=1608792&r2=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxSimilarityValueSource.java Tue Jul  8 14:15:35 2014
@@ -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);
-
-    return new FunctionValues() {
-      //reused
-      Rectangle rect = strategy.getSpatialContext().makeRectangle(0,0,0,0);
 
+    final FunctionValues shapeValues = bboxValueSource.getValues(context, readerContext);
+
+    return new DoubleDocValues(this) {
       @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 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 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");
+      public boolean exists(int doc) {
+        return shapeValues.exists(doc);
       }
 
       @Override
-      public String toString(int doc) {
-        return description() + "=" + floatVal(doc);
+      public Explanation explain(int 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 that = (BBoxSimilarityValueSource) o;
+
+    if (!bboxValueSource.equals(that.bboxValueSource)) return false;
 
-    BBoxSimilarityValueSource other = (BBoxSimilarityValueSource) o;
-    return similarity.equals(other.similarity);
+    return true;
   }
 
   @Override
   public int hashCode() {
-    return BBoxSimilarityValueSource.class.hashCode() + similarity.hashCode();
+    return bboxValueSource.hashCode();
   }
 }

Modified: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java?rev=1608793&r1=1608792&r2=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java Tue Jul  8 14:15:35 2014
@@ -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.Spatial
 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.U
  * 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 Spatia
   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
-
-  public double queryPower = 1.0;
-  public double targetPower = 1.0f;
-  public int precisionStep = 8; // same as solr default
+  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
+
+  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 Spatia
     field_minY = fieldNamePrefix + SUFFIX_MINY;
     field_maxY = fieldNamePrefix + SUFFIX_MAXY;
     field_xdl = fieldNamePrefix + SUFFIX_XDL;
-  }
 
-  public void setPrecisionStep( int p ) {
-    precisionStep = p;
-    if (precisionStep<=0 || precisionStep>=64)
-      precisionStep=Integer.MAX_VALUE;
+    FieldType fieldType = new FieldType(DoubleField.TYPE_NOT_STORED);
+    fieldType.setNumericPrecisionStep(8);//Solr's default
+    fieldType.setDocValueType(FieldInfo.DocValuesType.NUMERIC);
+    setFieldType(fieldType);
+  }
+
+  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 Spatia
     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 Spatia
     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 Spatia
     return spatial;
   }
 
-
-  //-------------------------------------------------------------------------------
-  //
-  //-------------------------------------------------------------------------------
-
   /**
    * Constructs a query to retrieve documents that fully contain the input envelope.
    *
@@ -209,8 +259,8 @@ public class BBoxStrategy extends Spatia
 
     // 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 Spatia
       // 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);
-
-      // apply the non-XDL and XDL conditions
-      xConditions = this.makeQuery(BooleanClause.Occur.SHOULD, qNonXDL, qXDL);
+      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);
+
+        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 Spatia
       // 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 Spatia
 
     // 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 Spatia
 
       // 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 Spatia
       // 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 Spatia
   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 Spatia
     // 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 Spatia
   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 Spatia
 
     // 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 Spatia
 
       // 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 Spatia
       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 Spatia
   /**
    * 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 Spatia
    * @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));
+  }
+
 }
 
 

Added: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxValueSource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxValueSource.java?rev=1608793&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxValueSource.java (added)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/bbox/BBoxValueSource.java Tue Jul  8 14:15:35 2014
@@ -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();
+  }
+}

Added: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeAreaValueSource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeAreaValueSource.java?rev=1608793&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeAreaValueSource.java (added)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/util/ShapeAreaValueSource.java Tue Jul  8 14:15:35 2014
@@ -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;
+  }
+
+}

Modified: lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java?rev=1608793&r1=1608792&r2=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java Tue Jul  8 14:15:35 2014
@@ -23,6 +23,7 @@ import com.spatial4j.core.context.Spatia
 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;
@@ -91,8 +92,19 @@ public class DistanceStrategyTest extend
   }
 
   @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

Modified: lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java?rev=1608793&r1=1608792&r2=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/SpatialTestCase.java Tue Jul  8 14:15:35 2014
@@ -70,10 +70,6 @@ public abstract class SpatialTestCase ex
     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);
 

Modified: lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java?rev=1608793&r1=1608792&r2=1608793&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java Tue Jul  8 14:15:35 2014
@@ -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();
-    this.ctx = SpatialContext.GEO;
-    this.strategy = new BBoxStrategy(ctx, "bbox");
+  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);
+    }
   }
 
-  /* Convert DATA_WORLD_CITIES_POINTS to bbox */
   @Override
-  protected Shape convertShapeFromGetDocuments(Shape shape) {
-    return shape.getBoundingBox();
+  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();
+  }
+
+  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);
+    }
+
+  }
+
+  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);
+  }
 }