lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gsing...@apache.org
Subject svn commit: r1041484 - in /lucene/dev/branches/branch_3x/solr: ./ src/java/org/apache/solr/handler/component/ src/java/org/apache/solr/schema/ src/java/org/apache/solr/search/ src/test/org/apache/solr/search/
Date Thu, 02 Dec 2010 17:38:58 GMT
Author: gsingers
Date: Thu Dec  2 17:38:58 2010
New Revision: 1041484

URL: http://svn.apache.org/viewvc?rev=1041484&view=rev
Log:
SOLR-1568: add bbox, implement sfilt to restrict to dist [from revision 1001129]

Added:
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialBoxQParserPlugin.java
      - copied unchanged from r1001129, lucene/dev/trunk/solr/src/java/org/apache/solr/search/SpatialBoxQParserPlugin.java
Modified:
    lucene/dev/branches/branch_3x/solr/   (props changed)
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/handler/component/QueryComponent.java
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/schema/LatLonType.java
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/QParserPlugin.java
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SolrIndexSearcher.java
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParser.java
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParserPlugin.java
    lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialOptions.java
    lucene/dev/branches/branch_3x/solr/src/test/org/apache/solr/search/SpatialFilterTest.java

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/handler/component/QueryComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/handler/component/QueryComponent.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/handler/component/QueryComponent.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/handler/component/QueryComponent.java
Thu Dec  2 17:38:58 2010
@@ -90,7 +90,7 @@ public class QueryComponent extends Sear
       if (fqs!=null && fqs.length!=0) {
         List<Query> filters = rb.getFilters();
         if (filters==null) {
-          filters = new ArrayList<Query>();
+          filters = new ArrayList<Query>(fqs.length);
           rb.setFilters( filters );
         }
         for (String fq : fqs) {

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/schema/LatLonType.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/schema/LatLonType.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/schema/LatLonType.java (original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/schema/LatLonType.java Thu
Dec  2 17:38:58 2010
@@ -18,17 +18,19 @@ package org.apache.solr.schema;
 
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Fieldable;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SortField;
+import org.apache.lucene.index.IndexReader;
+
+import org.apache.lucene.search.*;
 import org.apache.lucene.spatial.DistanceUtils;
 import org.apache.lucene.spatial.tier.InvalidGeoException;
+
 import org.apache.solr.common.SolrException;
 import org.apache.solr.response.TextResponseWriter;
 import org.apache.solr.response.XMLWriter;
 import org.apache.solr.search.QParser;
+import org.apache.solr.search.SolrIndexReader;
 import org.apache.solr.search.SpatialOptions;
+import org.apache.solr.search.function.DocValues;
 import org.apache.solr.search.function.ValueSource;
 import org.apache.solr.search.function.VectorValueSource;
 
@@ -36,6 +38,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 
 /**
@@ -84,173 +87,127 @@ public class LatLonType extends Abstract
 
 
   public Query createSpatialQuery(QParser parser, SpatialOptions options) {
-    BooleanQuery result = new BooleanQuery();
-    double[] point = new double[0];
+    double[] point = null;
     try {
       point = DistanceUtils.parseLatitudeLongitude(options.pointStr);
     } catch (InvalidGeoException e) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
     }
+
+    // lat & lon in degrees
+    double latCenter = point[LAT];
+    double lonCenter = point[LONG];
+
     point[0] = point[0] * DistanceUtils.DEGREES_TO_RADIANS;
     point[1] = point[1] * DistanceUtils.DEGREES_TO_RADIANS;
     //Get the distance
-    double[] ur = new double[2];
-    double[] ll = new double[2];
+
     double[] tmp = new double[2];
     //these calculations aren't totally accurate, but it should be good enough
     //TODO: Optimize to do in single calculations.  Would need to deal with poles, prime
meridian, etc.
     double [] north = DistanceUtils.pointOnBearing(point[LAT], point[LONG], options.distance,
0, tmp, options.radius);
     //This returns the point as radians, but we need degrees b/c that is what the field is
stored as
-    ur[LAT] = north[LAT] * DistanceUtils.RADIANS_TO_DEGREES;//get it now, as we are going
to reuse tmp
+    double ur_lat = north[LAT] * DistanceUtils.RADIANS_TO_DEGREES;//get it now, as we are
going to reuse tmp
     double [] east = DistanceUtils.pointOnBearing(point[LAT], point[LONG], options.distance,
DistanceUtils.DEG_90_AS_RADS, tmp, options.radius);
-    ur[LONG] = east[LONG] * DistanceUtils.RADIANS_TO_DEGREES;
+    double ur_lon = east[LONG] * DistanceUtils.RADIANS_TO_DEGREES;
     double [] south = DistanceUtils.pointOnBearing(point[LAT], point[LONG], options.distance,
DistanceUtils.DEG_180_AS_RADS, tmp, options.radius);
-    ll[LAT] = south[LAT] * DistanceUtils.RADIANS_TO_DEGREES;
+    double ll_lat = south[LAT] * DistanceUtils.RADIANS_TO_DEGREES;
     double [] west = DistanceUtils.pointOnBearing(point[LAT], point[LONG], options.distance,
DistanceUtils.DEG_270_AS_RADS, tmp, options.radius);
-    ll[LONG] = west[LONG] * DistanceUtils.RADIANS_TO_DEGREES;
+    double ll_lon = west[LONG] * DistanceUtils.RADIANS_TO_DEGREES;
+    
 
-    SchemaField subSF;
-    Query range;
     //TODO: can we reuse our bearing calculations?
     double angDist = DistanceUtils.angularDistance(options.distance,
             options.radius);//in radians
 
-    
-    //for the poles, do something slightly different
-    //Also, note point[LAT] is in radians, but ur and ll are in degrees
-    if (point[LAT] + angDist > DistanceUtils.DEG_90_AS_RADS) { //we cross the north pole
+    double latMin = -90.0, latMax = 90.0, lonMin = -180.0, lonMax = 180.0;
+    double lon2Min = -180.0, lon2Max = 180.0;  // optional second longitude restriction
+
+    // for the poles, do something slightly different - a polar "cap".
+    // Also, note point[LAT] is in radians, but ur and ll are in degrees
+    if (point[LAT] + angDist > DistanceUtils.DEG_90_AS_RADS) { // we cross the north pole
       //we don't need a longitude boundary at all
+      latMin = Math.min(ll_lat, ur_lat);
+    } else if (point[LAT] - angDist < -DistanceUtils.DEG_90_AS_RADS) { // we cross the
south pole
+      latMax = Math.max(ll_lat, ur_lat);
+    } else {
+      // set the latitude restriction as normal
+      latMin = ll_lat;
+      latMax = ur_lat;
+
+      if (ll_lon > ur_lon) {
+         // we crossed the +-180 deg longitude... need to make
+        // range queries of (-180 TO ur) OR (ll TO 180)
+        lonMin = -180;
+        lonMax = ur_lon;
+        lon2Min = ll_lon;
+        lon2Max = 180;
+      } else {
+        lonMin = ll_lon;
+        lonMax = ur_lon;
+      }
+    }
+
+
+    // Now that we've figured out the ranges, build them!
+    SchemaField latField = subField(options.field, LAT);
+    SchemaField lonField = subField(options.field, LONG);
+
+    if (options.bbox) {
+      BooleanQuery result = new BooleanQuery();  // only used if box==true
+
+      Query latRange = latField.getType().getRangeQuery(parser, latField,
+                String.valueOf(latMin),
+                String.valueOf(latMax),
+                true, true);
+      result.add(latRange, BooleanClause.Occur.MUST);
+
+      if (lonMin != -180 || lonMax != 180) {
+        Query lonRange = lonField.getType().getRangeQuery(parser, lonField,
+                String.valueOf(lonMin),
+                String.valueOf(lonMax),
+                true, true);
+        if (lon2Min != -180 || lon2Max != 180) {
+          // another valid longitude range
+          BooleanQuery bothLons = new BooleanQuery();
+          bothLons.add(lonRange, BooleanClause.Occur.SHOULD);
+
+          lonRange = lonField.getType().getRangeQuery(parser, lonField,
+                String.valueOf(lon2Min),
+                String.valueOf(lon2Max),
+                true, true);
+          bothLons.add(lonRange, BooleanClause.Occur.SHOULD);
 
-      double minLat = Math.min(ll[LAT], ur[LAT]);
-      subSF = subField(options.field, LAT);
-      range = subSF.getType().getRangeQuery(parser, subSF,
-              String.valueOf(minLat),
-              "90", true, true);
-      result.add(range, BooleanClause.Occur.MUST);
-    } else if (point[LAT] - angDist < -DistanceUtils.DEG_90_AS_RADS) {//we cross the south
pole
-      subSF = subField(options.field, LAT);
-      double maxLat = Math.max(ll[LAT], ur[LAT]);
-      range = subSF.getType().getRangeQuery(parser, subSF,
-              "-90", String.valueOf(maxLat), true, true);
-      result.add(range, BooleanClause.Occur.MUST);
-    } else{
-        //Latitude
-        //we may need to generate multiple queries depending on the range
-        //Are we crossing the 180 deg. longitude, if so, we need to do some special things
-        if (ll[LONG] > 0.0 && ur[LONG] < 0.0) {
-          //TODO: refactor into common code, etc.
-          //Now check other side of the Equator
-          if (ll[LAT] < 0.0 && ur[LAT] > 0.0) {
-            addEquatorialBoundary(parser, options, result, ur[LAT], ll[LAT]);
-          } //check poles
-          else {
-            subSF = subField(options.field, LAT);
-            //not crossing the equator
-            range = subSF.getType().getRangeQuery(parser, subSF,
-                    String.valueOf(ll[LAT]),
-                    String.valueOf(ur[LAT]), true, true);
-            result.add(range, BooleanClause.Occur.MUST);
-          }
-          //Longitude
-          addMeridianBoundary(parser, options, result, ur[LONG], ll[LONG], "180.0", "-180.0");
-
-        } else if (ll[LONG] < 0.0 && ur[LONG] > 0.0) {//prime meridian (0 degrees
-          //Now check other side of the Equator
-          if (ll[LAT] < 0.0 && ur[LAT] > 0.0) {
-            addEquatorialBoundary(parser, options, result, ur[LAT], ll[LAT]);
-          } else {
-            subSF = subField(options.field, LAT);
-            //not crossing the equator
-            range = subSF.getType().getRangeQuery(parser, subSF,
-                    String.valueOf(ll[LAT]),
-                    String.valueOf(ur[LAT]), true, true);
-            result.add(range, BooleanClause.Occur.MUST);
-          }
-          //Longitude
-          addMeridianBoundary(parser, options, result, ur[LONG], ll[LONG], "0.0", ".0");
-
-        } else {// we are all in the Eastern or Western hemi
-          //Now check other side of the Equator
-          if (ll[LAT] < 0.0 && ur[LAT] > 0.0) {
-            addEquatorialBoundary(parser, options, result, ur[LAT], ll[LAT]);
-          } else {//we are all in either the Northern or the Southern Hemi.
-            //TODO: nice to move this up so that it is the first thing and we can avoid the
extra checks since
-            //this is actually the most likely case
-            subSF = subField(options.field, LAT);
-            range = subSF.getType().getRangeQuery(parser, subSF,
-                    String.valueOf(ll[LAT]),
-                    String.valueOf(ur[LAT]), true, true);
-            result.add(range, BooleanClause.Occur.MUST);
-
-          }
-          //Longitude, all in the same hemi
-          subSF = subField(options.field, LONG);
-          range = subSF.getType().getRangeQuery(parser, subSF,
-                  String.valueOf(ll[LONG]),
-                  String.valueOf(ur[LONG]), true, true);
-          result.add(range, BooleanClause.Occur.MUST);
+          lonRange = bothLons;
         }
+
+        result.add(lonRange, BooleanClause.Occur.MUST);
       }
 
       return result;
     }
 
-    /**
-     * Add a boundary condition around a meridian
-     * @param parser
-     * @param options
-     * @param result
-     * @param upperRightLon
-     * @param lowerLeftLon
-     * @param eastern
-     * @param western
-     */
-
-  private void addMeridianBoundary(QParser parser, SpatialOptions options, BooleanQuery result,
double upperRightLon,
-                                    double lowerLeftLon, String eastern, String western)
{
-    SchemaField subSF;
-    Query range;
-    BooleanQuery lonQ = new BooleanQuery();
-    subSF = subField(options.field, LONG);
-    //Eastern Hemisphere
-    range = subSF.getType().getRangeQuery(parser, subSF,
-            String.valueOf(lowerLeftLon),
-            eastern, true, true);
-    lonQ.add(range, BooleanClause.Occur.SHOULD);
-    //Western hemi
-    range = subSF.getType().getRangeQuery(parser, subSF,
-            western,
-            String.valueOf(upperRightLon), true, true);
-    lonQ.add(range, BooleanClause.Occur.SHOULD);
-    //One or the other must occur
-    result.add(lonQ, BooleanClause.Occur.MUST);
-  }
-
-  /**
-   * Add query conditions for boundaries like the equator, poles and meridians
-   *
-   * @param parser
-   * @param options
-   * @param result
-   * @param upperRight
-   * @param lowerLeft
-   */
-  protected void addEquatorialBoundary(QParser parser, SpatialOptions options, BooleanQuery
result, double upperRight, double lowerLeft) {
-    SchemaField subSF;
-    Query range;
-    BooleanQuery tmpQ = new BooleanQuery();
-    subSF = subField(options.field, LAT);
-    //southern hemi.
-    range = subSF.getType().getRangeQuery(parser, subSF,
-            String.valueOf(lowerLeft),
-            "0", true, true);
-    tmpQ.add(range, BooleanClause.Occur.SHOULD);
-    //northern hemi
-    range = subSF.getType().getRangeQuery(parser, subSF,
-            "0", String.valueOf(upperRight), true, true);
-    tmpQ.add(range, BooleanClause.Occur.SHOULD);
-    //One or the other must occur
-    result.add(tmpQ, BooleanClause.Occur.MUST);
+
+    SpatialDistanceQuery spatial = new SpatialDistanceQuery();
+    spatial.origField = options.field.getName();
+    spatial.latSource = latField.getType().getValueSource(latField, parser);
+    spatial.lonSource = lonField.getType().getValueSource(lonField, parser);
+    spatial.latMin = latMin;
+    spatial.latMax = latMax;
+    spatial.lonMin = lonMin;
+    spatial.lonMax = lonMax;
+    spatial.lon2Min = lon2Min;
+    spatial.lon2Max = lon2Max;
+    spatial.lon2 = lon2Min != -180 || lon2Max != 180;
+
+    spatial.latCenter = latCenter;
+    spatial.lonCenter = lonCenter;
+    spatial.dist = options.distance;
+    spatial.planetRadius = options.radius;
+
+    spatial.calcDist = !options.bbox;
+
+    return spatial;
   }
 
   @Override
@@ -312,3 +269,292 @@ class LatLonValueSource extends VectorVa
     return name() + "(" + sf.getName() + ")";
   }
 }
+
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// TODO: recast as a value source that doesn't have to match all docs
+
+class SpatialDistanceQuery extends Query {
+  String origField;
+  ValueSource latSource;
+  ValueSource lonSource;
+  double lonMin, lonMax, lon2Min, lon2Max, latMin, latMax;
+  boolean lon2;
+
+  boolean calcDist;  // actually calculate the distance with haversine
+
+  double latCenter;
+  double lonCenter;
+  double dist;
+  double planetRadius;
+
+
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    return this;
+  }
+
+  @Override
+  public void extractTerms(Set terms) {}
+
+  protected class SpatialWeight extends Weight {
+    protected Searcher searcher;
+    protected float queryNorm;
+    protected float queryWeight;
+    protected Map latContext;
+    protected Map lonContext;
+
+    public SpatialWeight(Searcher searcher) throws IOException {
+      this.searcher = searcher;
+      this.latContext = latSource.newContext();
+      this.lonContext = lonSource.newContext();
+      latSource.createWeight(latContext, searcher);
+      lonSource.createWeight(lonContext, searcher);
+    }
+
+    @Override
+    public Query getQuery() {
+      return SpatialDistanceQuery.this;
+    }
+
+    @Override
+    public float getValue() {
+      return queryWeight;
+    }
+
+    @Override
+    public float sumOfSquaredWeights() throws IOException {
+      queryWeight = getBoost();
+      return queryWeight * queryWeight;
+    }
+
+    @Override
+    public void normalize(float norm) {
+      this.queryNorm = norm;
+      queryWeight *= this.queryNorm;
+    }
+
+    @Override
+    public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer)
throws IOException {
+      return new SpatialScorer(getSimilarity(searcher), reader, this);
+    }
+
+    @Override
+    public Explanation explain(IndexReader reader, int doc) throws IOException {
+      SolrIndexReader topReader = (SolrIndexReader)reader;
+      SolrIndexReader[] subReaders = topReader.getLeafReaders();
+      int[] offsets = topReader.getLeafOffsets();
+      int readerPos = SolrIndexReader.readerIndex(doc, offsets);
+      int readerBase = offsets[readerPos];
+      return ((SpatialScorer)scorer(subReaders[readerPos], true, true)).explain(doc-readerBase);
+    }
+  }
+
+  protected class SpatialScorer extends Scorer {
+    final IndexReader reader;
+    final SpatialWeight weight;
+    final int maxDoc;
+    final float qWeight;
+    int doc=-1;
+    final DocValues latVals;
+    final DocValues lonVals;
+
+
+
+    final double lonMin, lonMax, lon2Min, lon2Max, latMin, latMax;
+    final boolean lon2;
+    final boolean calcDist;
+    
+    final double latCenterRad;
+    final double lonCenterRad;
+    final double latCenterRad_cos;
+    final double dist;
+    final double planetRadius;
+
+    int lastDistDoc;
+    double lastDist;
+
+    public SpatialScorer(Similarity similarity, IndexReader reader, SpatialWeight w) throws
IOException {
+      super(similarity);
+      this.weight = w;
+      this.qWeight = w.getValue();
+      this.reader = reader;
+      this.maxDoc = reader.maxDoc();
+
+      latVals = latSource.getValues(weight.latContext, reader);
+      lonVals = lonSource.getValues(weight.lonContext, reader);
+
+      this.lonMin = SpatialDistanceQuery.this.lonMin;
+      this.lonMax = SpatialDistanceQuery.this.lonMax;
+      this.lon2Min = SpatialDistanceQuery.this.lon2Min;
+      this.lon2Max = SpatialDistanceQuery.this.lon2Max;
+      this.latMin = SpatialDistanceQuery.this.latMin;
+      this.latMax = SpatialDistanceQuery.this.latMax;
+      this.lon2 = SpatialDistanceQuery.this.lon2;
+      this.calcDist = SpatialDistanceQuery.this.calcDist;
+
+      this.latCenterRad = SpatialDistanceQuery.this.latCenter * DistanceUtils.DEGREES_TO_RADIANS;
+      this.lonCenterRad = SpatialDistanceQuery.this.lonCenter * DistanceUtils.DEGREES_TO_RADIANS;
+      this.latCenterRad_cos = this.calcDist ? Math.cos(latCenterRad) : 0;
+      this.dist = SpatialDistanceQuery.this.dist;
+      this.planetRadius = SpatialDistanceQuery.this.planetRadius;
+
+    }
+
+    boolean match() {
+      // longitude should generally be more restrictive than latitude
+      // (e.g. in the US, it immediately separates the coasts, and in world search separates
+      // US from Europe from Asia, etc.
+      double lon = lonVals.doubleVal(doc);
+      if (! ((lon >= lonMin && lon <=lonMax) || (lon2 && lon >=
lon2Min && lon <= lon2Max)) ) {
+        return false;
+      }
+
+      double lat = latVals.doubleVal(doc);
+      if ( !(lat >= latMin && lat <= latMax) ) {
+        return false;
+      }
+
+      if (!calcDist) return true;
+
+      // TODO: test for internal box where we wouldn't need to calculate the distance
+
+      return dist(lat, lon) <= dist;
+    }
+
+    double dist(double lat, double lon) {
+      double latRad = lat * DistanceUtils.DEGREES_TO_RADIANS;
+      double lonRad = lon * DistanceUtils.DEGREES_TO_RADIANS;
+      
+      // haversine, specialized to avoid a cos() call on latCenterRad
+      double diffX = latCenterRad - latRad;
+      double diffY = lonCenterRad - lonRad;
+      double hsinX = Math.sin(diffX * 0.5);
+      double hsinY = Math.sin(diffY * 0.5);
+      double h = hsinX * hsinX +
+              (latCenterRad_cos * Math.cos(latRad) * hsinY * hsinY);
+      double result = (planetRadius * 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h)));
+
+      // save the results of this calculation
+      lastDistDoc = doc;
+      lastDist = result;
+      
+      return result;
+    }
+
+    @Override
+    public int docID() {
+      return doc;
+    }
+
+    // instead of matching all docs, we could also embed a query.
+    // the score could either ignore the subscore, or boost it.
+    // Containment:  floatline(foo:myTerm, "myFloatField", 1.0, 0.0f)
+    // Boost:        foo:myTerm^floatline("myFloatField",1.0,0.0f)
+    @Override
+    public int nextDoc() throws IOException {
+      for(;;) {
+        ++doc;
+        if (doc>=maxDoc) {
+          return doc=NO_MORE_DOCS;
+        }
+        //TODO: In trunk, there is a faster way of doing this, what about here?
+        if (reader.isDeleted(doc)/*delDocs != null && delDocs.get(doc)*/) continue;
+        if (!match()) continue;
+        return doc;
+      }
+    }
+
+    @Override
+    public int advance(int target) throws IOException {
+      // this will work even if target==NO_MORE_DOCS
+      doc=target-1;
+      return nextDoc();
+    }
+
+    @Override
+    public float score() throws IOException {
+      double dist = (doc == lastDistDoc) ? lastDist : dist(latVals.doubleVal(doc), lonVals.doubleVal(doc));
+      return (float)(dist * qWeight);
+    }
+
+    public Explanation explain(int doc) throws IOException {
+      advance(doc);
+      boolean matched = this.doc == doc;
+      this.doc = doc;
+
+      float sc = matched ? score() : 0;
+      double dist = dist(latVals.doubleVal(doc), lonVals.doubleVal(doc));
+
+      String description = SpatialDistanceQuery.this.toString();
+
+      Explanation result = new ComplexExplanation
+        (this.doc == doc, sc, description +  " product of:");
+      // result.addDetail(new Explanation((float)dist, "hsin("+latVals.explain(doc)+","+lonVals.explain(doc)));
+      result.addDetail(new Explanation((float)dist, "hsin("+latVals.doubleVal(doc)+","+lonVals.doubleVal(doc)));
+      result.addDetail(new Explanation(getBoost(), "boost"));
+      result.addDetail(new Explanation(weight.queryNorm,"queryNorm"));
+      return result;
+    }
+  }
+
+
+  @Override
+  public Weight createWeight(Searcher searcher) throws IOException {
+    return new SpatialWeight(searcher);
+  }
+
+
+  /** Prints a user-readable version of this query. */
+  @Override
+  public String toString(String field)
+  {
+    float boost = getBoost();
+    return (boost!=1.0?"(":"") +
+            "sfilt(latlonSource="+origField +"(" + latSource + "," + lonSource + ")"
+            +",latCenter="+latCenter+",lonCenter="+lonCenter
+            +",dist=" + dist
+            +",latMin=" + latMin + ",latMax="+latMax
+            +",lonMin=" + lonMin + ",lonMax"+lonMax
+            +",lon2Min=" + lon2Min + ",lon2Max" + lon2Max
+            +",calcDist="+calcDist
+            +",planetRadius="+planetRadius
+            +")"
+            + (boost==1.0 ? "" : ")^"+boost);
+  }
+
+
+  /** Returns true if <code>o</code> is equal to this. */
+  @Override
+  public boolean equals(Object o) {
+    if (SpatialDistanceQuery.class != o.getClass()) return false;
+    SpatialDistanceQuery other = (SpatialDistanceQuery)o;
+    return     this.latCenter == other.latCenter
+            && this.lonCenter == other.lonCenter
+            && this.latMin == other.latMin
+            && this.latMax == other.latMax
+            && this.lonMin == other.lonMin
+            && this.lonMax == other.lonMax
+            && this.lon2Min == other.lon2Min
+            && this.lon2Max == other.lon2Max
+            && this.dist == other.dist
+            && this.planetRadius == other.planetRadius
+            && this.calcDist == other.calcDist
+            && this.lonSource.equals(other.lonSource)
+            && this.latSource.equals(other.latSource)
+            && this.getBoost() == other.getBoost()
+        ;
+  }
+
+  /** Returns a hash code value for this object. */
+  @Override
+  public int hashCode() {
+    // don't bother making the hash expensive - the center latitude + min longitude will
be very uinque 
+    long hash = Double.doubleToLongBits(latCenter);
+    hash = hash * 31 + Double.doubleToLongBits(lonMin);
+    return (int)(hash >> 32 + hash);
+  }
+
+}
+
+

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/QParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/QParserPlugin.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/QParserPlugin.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/QParserPlugin.java
Thu Dec  2 17:38:58 2010
@@ -38,6 +38,7 @@ public abstract class QParserPlugin impl
     NestedQParserPlugin.NAME, NestedQParserPlugin.class,
     FunctionRangeQParserPlugin.NAME, FunctionRangeQParserPlugin.class,
     SpatialFilterQParserPlugin.NAME, SpatialFilterQParserPlugin.class,
+    SpatialBoxQParserPlugin.NAME, SpatialBoxQParserPlugin.class,
   };
 
   /** return a {@link QParser} */

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SolrIndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SolrIndexSearcher.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SolrIndexSearcher.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SolrIndexSearcher.java
Thu Dec  2 17:38:58 2010
@@ -649,6 +649,80 @@ public class SolrIndexSearcher extends I
     return answer;
   }
 
+  Filter getFilter(Query q) throws IOException {
+    if (q == null) return null;
+    // TODO: support pure negative queries?
+
+    // if (q instanceof) {
+    // }
+
+    return getDocSet(q).getTopFilter();
+  }
+
+
+  Filter getFilter(DocSet setFilter, List<Query> queries) throws IOException {
+    Filter answer = setFilter == null ? null : setFilter.getTopFilter();
+
+    if (queries == null || queries.size() == 0) {
+      return answer;
+    }
+
+    if (answer == null && queries.size() == 1) {
+      return getFilter(queries.get(0));  
+    }
+
+
+    DocSet finalSet=null;
+
+    int nDocSets =0;
+    boolean[] neg = new boolean[queries.size()];
+    DocSet[] sets = new DocSet[queries.size()];
+    Query[] nocache = new Query[queries.size()];
+
+    int smallestIndex = -1;
+    int smallestCount = Integer.MAX_VALUE;
+    for (Query q : queries) {
+      // if (q instanceof)
+
+
+      Query posQuery = QueryUtils.getAbs(q);
+      sets[nDocSets] = getPositiveDocSet(posQuery);
+      // Negative query if absolute value different from original
+      if (q==posQuery) {
+        neg[nDocSets] = false;
+        // keep track of the smallest positive set.
+        // This optimization is only worth it if size() is cached, which it would
+        // be if we don't do any set operations.
+        int sz = sets[nDocSets].size();
+        if (sz<smallestCount) {
+          smallestCount=sz;
+          smallestIndex=nDocSets;
+          finalSet = sets[nDocSets];
+        }
+      } else {
+        neg[nDocSets] = true;
+      }
+
+      nDocSets++;
+    }
+
+    // if no positive queries, start off with all docs
+    if (finalSet==null) finalSet = getPositiveDocSet(matchAllDocsQuery);
+
+    // do negative queries first to shrink set size
+    for (int i=0; i<sets.length; i++) {
+      if (neg[i]) finalSet = finalSet.andNot(sets[i]);
+    }
+
+    for (int i=0; i<sets.length; i++) {
+      if (!neg[i] && i!=smallestIndex) finalSet = finalSet.intersection(sets[i]);
+    }
+
+    return finalSet.getTopFilter();
+
+
+  }
+
   // query must be positive
   protected DocSet getDocSetNC(Query query, DocSet filter) throws IOException {
     DocSetCollector collector = new DocSetCollector(maxDoc()>>6, maxDoc());

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParser.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParser.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParser.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParser.java
Thu Dec  2 17:38:58 2010
@@ -53,10 +53,11 @@ import org.apache.solr.schema.SpatialQue
  *
  */
 public class SpatialFilterQParser extends QParser {
+  boolean bbox;  // do bounding box only
 
-
-  public SpatialFilterQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest
req) {
+  public SpatialFilterQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest
req, boolean bbox) {
     super(qstr, localParams, params, req);
+    this.bbox = bbox;
   }
 
   
@@ -97,6 +98,7 @@ public class SpatialFilterQParser extend
       if (type instanceof SpatialQueryable) {
         double radius = localParams.getDouble(SpatialParams.SPHERE_RADIUS, DistanceUtils.EARTH_MEAN_RADIUS_KM);
         SpatialOptions opts = new SpatialOptions(pointStr, dist, sf, measStr, radius, DistanceUnits.KILOMETERS);
+        opts.bbox = bbox;
         result = ((SpatialQueryable)type).createSpatialQuery(this, opts);
       } else {
         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field " + fields[0]

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParserPlugin.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParserPlugin.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialFilterQParserPlugin.java
Thu Dec  2 17:38:58 2010
@@ -27,12 +27,11 @@ import org.apache.solr.request.SolrQuery
 public class SpatialFilterQParserPlugin extends QParserPlugin {
   public static String NAME = "sfilt";
 
-
   @Override
   public QParser createParser(String qstr, SolrParams localParams,
                               SolrParams params, SolrQueryRequest req) {
 
-    return new SpatialFilterQParser(qstr, localParams, params, req);
+    return new SpatialFilterQParser(qstr, localParams, params, req, false);
   }
 
   public void init(NamedList args) {
@@ -40,3 +39,4 @@ public class SpatialFilterQParserPlugin 
   }
 
 }
+

Modified: lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialOptions.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialOptions.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialOptions.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/java/org/apache/solr/search/SpatialOptions.java
Thu Dec  2 17:38:58 2010
@@ -32,6 +32,11 @@ public class SpatialOptions {
   public double radius;
   public DistanceUnits units;
 
+  /** Just do a "bounding box" - or any other quicker method / shape that
+   * still encompasses all of the points of interest, but may also encompass
+   * points outside.
+   */ 
+  public boolean bbox;
 
   public SpatialOptions() {
   }

Modified: lucene/dev/branches/branch_3x/solr/src/test/org/apache/solr/search/SpatialFilterTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/src/test/org/apache/solr/search/SpatialFilterTest.java?rev=1041484&r1=1041483&r2=1041484&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/src/test/org/apache/solr/search/SpatialFilterTest.java
(original)
+++ lucene/dev/branches/branch_3x/solr/src/test/org/apache/solr/search/SpatialFilterTest.java
Thu Dec  2 17:38:58 2010
@@ -96,7 +96,7 @@ public class SpatialFilterTest extends S
     //Because we are generating a box based on the west/east longitudes and the south/north
latitudes, which then
     //translates to a range query, which is slightly more inclusive.  Thus, even though 0.0
is 15.725 kms away,
     //it will be included, b/c of the box calculation.
-    checkHits(fieldName, "0.1,0.1", 15, 2, 5, 6);
+    checkHits(fieldName, false, "0.1,0.1", 15, 2, 5, 6);
    //try some more
     clearIndex();
     assertU(adoc("id", "14", fieldName, "0,5"));
@@ -108,15 +108,23 @@ public class SpatialFilterTest extends S
 
     checkHits(fieldName, "0,0", 1000, 1, 14);
     checkHits(fieldName, "0,0", 2000, 2, 14, 15);
-    checkHits(fieldName, "0,0", 3000, 3, 14, 15, 16);
+    checkHits(fieldName, false, "0,0", 3000, 3, 14, 15, 16);
     checkHits(fieldName, "0,0", 3001, 3, 14, 15, 16);
     checkHits(fieldName, "0,0", 3000.1, 3, 14, 15, 16);
+
     //really fine grained distance and reflects some of the vagaries of how we are calculating
the box
     checkHits(fieldName, "43.517030,-96.789603", 109, 0);
-    checkHits(fieldName, "43.517030,-96.789603", 110, 1, 17);
+
+    // falls outside of the real distance, but inside the bounding box   
+    checkHits(fieldName, true, "43.517030,-96.789603", 110, 0);
+    checkHits(fieldName, false, "43.517030,-96.789603", 110, 1, 17);
   }
 
   private void checkHits(String fieldName, String pt, double distance, int count, int ...
docIds) {
+    checkHits(fieldName, true, pt, distance, count, docIds);
+  }
+
+  private void checkHits(String fieldName, boolean exact, String pt, double distance, int
count, int ... docIds) {
     String [] tests = new String[docIds != null && docIds.length > 0 ? docIds.length
+ 1 : 1];
     tests[0] = "*[count(//doc)=" + count + "]";
     if (docIds != null && docIds.length > 0) {
@@ -125,9 +133,12 @@ public class SpatialFilterTest extends S
         tests[i++] = "//result/doc/int[@name='id'][.='" + docId + "']";
       }
     }
-    assertQ(req("fl", "id", "q","*:*", "rows", "1000", "fq", "{!sfilt fl=" +fieldName +"}",
-            "pt", pt, "d", String.valueOf(distance)),
-            tests);//
+
+    String method = exact ? "sfilt" : "bbox";
+
+    assertQ(req("fl", "id", "q","*:*", "rows", "1000", "fq", "{!"+method+" fl=" +fieldName
+"}",
+              "pt", pt, "d", String.valueOf(distance)),
+              tests);
   }
 
 



Mime
View raw message