Index: contrib/spatial/src/java/org/apache/lucene/spatial/SpatialFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/SpatialFilter.java	Sun Dec 20 21:10:57 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/SpatialFilter.java	Sun Dec 20 21:10:57 CET 2009
@@ -0,0 +1,256 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.spatial;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.*;
+import org.apache.lucene.spatial.distance.*;
+import org.apache.lucene.spatial.geometry.DistanceUnits;
+import org.apache.lucene.spatial.geometry.GeoDistanceCalculator;
+import org.apache.lucene.spatial.tier.CartesianPolyFilterBuilder;
+import org.apache.lucene.util.DocIdBitSet;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * SpatialFilter is a proper Lucene Filter that filters out documents that are outside of a certain radius of a central
+ * point.  At the heart of the filtering process is the {@link org.apache.lucene.spatial.tier.CartesianShapeFilter} which
+ * uses cartesian tiers to filter out the majority of documents.  The remaining documents are sent through an instance of
+ * {@link DistanceFilter} which calculates the actual distance of each document.
+ * <p/>
+ * For maximum compatability, distances in both miles and kilometers are supported and for maximum performance and for
+ * performance it is also possible to disabled the {@link org.apache.lucene.spatial.distance.DistanceFilter} step.
+ *
+ * <p><font color="red"><b>NOTE:</b> This API is still in flux and might change in incompatible ways in the next release.</font>
+ */
+public class SpatialFilter extends Filter {
+
+  private DistanceFilter distanceFilter;
+  private Filter cartesianFilter;
+  private Query query;
+
+  /**
+   * Creates a new SpatialFilter which will filter out documents that are outside of the given radius from the central
+   * point defined by the given latitude and longitude
+   *
+   * @param lat Latitude of the central point
+   * @param lng Longitude of the central point
+   * @param radius Radius from the central point that document must be within in order to pass the filter
+   * @param tierFieldPrefix Prefix of the fields that contain the cartesian tier information
+   * @param query Query that is going to executed along with the Filter.  This query is executed inside the filter to
+   * reduce the number of documents whose distances need to be calculated
+   */
+  public SpatialFilter(double lat, double lng, double radius, String tierFieldPrefix, Query query) {
+    this.query = query;
+    CartesianPolyFilterBuilder shapeFilterBuilder = new CartesianPolyFilterBuilder(tierFieldPrefix);
+    cartesianFilter = shapeFilterBuilder.buildFilter(lat, lng, radius);
+    distanceFilter = new NoOpDistanceFilter();
+  }
+
+  /**
+   * Creates a new SpatialFilter that uses latitude and longitude information stored in two separate fields
+   *
+   * @param lat Latitude of the central point
+   * @param lng Longitude of the central point
+   * @param radius Radius that a document must be within in order to pass the filter
+   * @param unit Unit of distance the radius is in
+   * @param latField Name of the field with the latitude values
+   * @param lngField Name of the field with the longitude values
+   * @param tierFieldPrefix Prefix of the fields that contain the cartesian tier information
+   * @param distanceCalculator GeoDistanceCalculator that will be used to calculate the distances between points in the
+   *                           distance filtering process
+   * @param executorService ExecutorService that will be used to manage any threads created as part of the distance
+   *                        filtering process
+   * @param threadCount Number of threads that the distance filtering process can use to multi-thread its work
+   * @param query Query that is going to executed along with the Filter.  This query is executed inside the filter to
+   *              reduce the number of documents whose distances need to be calculated
+   */
+  public SpatialFilter(
+      double lat,
+      double lng,
+      double radius,
+      DistanceUnits unit,
+      String latField,
+      String lngField,
+      String tierFieldPrefix,
+      GeoDistanceCalculator distanceCalculator,
+      ExecutorService executorService,
+      int threadCount, Query query) {
+
+    this(lat, lng, radius, tierFieldPrefix, query);
+    LocationDataSetFactory dataSetFactory = new LatLongLocationDataSetFactory(latField, lngField);
+    distanceFilter = new ThreadedDistanceFilter(
+        lat,
+        lng,
+        radius,
+        unit,
+        dataSetFactory,
+        distanceCalculator,
+        executorService,
+        threadCount);
+  }
+
+  /**
+   * Creates a new SpatialFilter that uses latitude and longitude information stored in two separate fields.  This method
+   * assumes that all calculations are to be executed in the calling thread
+   *
+   * @param lat Latitude of the central point
+   * @param lng Longitude of the central point
+   * @param radius Radius that a document must be within in order to pass the filter
+   * @param unit Unit of distance the radius is in
+   * @param latField Name of the field with the latitude values
+   * @param lngField Name of the field with the longitude values
+   * @param tierFieldPrefix Prefix of the fields that contain the cartesian tier information
+   * @param distanceCalculator GeoDistanceCalculator that will be used to calculate the distances between points in the
+   * @param query Query that is going to executed along with the Filter.  This query is executed inside the filter to
+   *              reduce the number of documents whose distances need to be calculated
+   */
+  public SpatialFilter(
+      double lat,
+      double lng,
+      double radius,
+      DistanceUnits unit,
+      String latField,
+      String lngField,
+      String tierFieldPrefix,
+      GeoDistanceCalculator distanceCalculator, Query query) {
+
+    this(lat,
+        lng,
+        radius,
+        unit,
+        latField,
+        lngField,
+        tierFieldPrefix,
+        distanceCalculator,
+        new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy()),
+        1,
+        query);
+  }
+
+  /**
+   * Creates a new SpatialFilter that uses latitude and longitude information stored in a geohash in a single field
+   *
+   * @param lat Latitude of the central point
+   * @param lng Longitude of the central point
+   * @param radius Radius that a document must be within in order to pass the filter
+   * @param unit Unit of distance the radius is in
+   * @param geoHashField Name of the field containing the geohash information
+   * @param tierFieldPrefix Prefix of the fields that contain the cartesian tier information
+   * @param distanceCalculator GeoDistanceCalculator that will be used to calculate the distances between points in the
+   *                           distance filtering process
+   * @param executorService ExecutorService that will be used to manage any threads created as part of the distance
+   *                        filtering process
+   * @param threadCount Number of threads that the distance filtering process can use to multi-thread its work
+   * @param query Query that is going to executed along with the Filter.  This query is executed inside the filter to
+   *              reduce the number of documents whose distances need to be calculated
+   */
+  public SpatialFilter(
+      double lat,
+      double lng,
+      double radius,
+      DistanceUnits unit,
+      String geoHashField,
+      String tierFieldPrefix,
+      GeoDistanceCalculator distanceCalculator,
+      ExecutorService executorService,
+      int threadCount, Query query) {
+
+    this(lat, lng, radius, tierFieldPrefix, query);
+    LocationDataSetFactory dataSetFactory = new GeoHashLocationDataSetFactory(geoHashField);
+    distanceFilter = new ThreadedDistanceFilter(
+        lat,
+        lng,
+        radius,
+        unit,
+        dataSetFactory,
+        distanceCalculator,
+        executorService,
+        threadCount);
+  }
+
+  /**
+   * Creates a new SpatialFilter that uses latitude and longitude information stored in a geohash in a single field.
+   * This method assumes that all calculations are to be executed in the calling thread
+   *
+   * @param lat Latitude of the central point
+   * @param lng Longitude of the central point
+   * @param radius Radius that a document must be within in order to pass the filter
+   * @param unit Unit of distance the radius is in
+   * @param geoHashField Name of the field containing the geohash information
+   * @param tierFieldPrefix Prefix of the fields that contain the cartesian tier information
+   * @param distanceCalculator GeoDistanceCalculator that will be used to calculate the distances between points in the
+   * @param query Query that is going to executed along with the Filter.  This query is executed inside the filter to
+   *              reduce the number of documents whose distances need to be calculated
+   */
+  public SpatialFilter(
+      double lat,
+      double lng,
+      double radius,
+      DistanceUnits unit,
+      String geoHashField,
+      String tierFieldPrefix,
+      GeoDistanceCalculator distanceCalculator, Query query) {
+
+    this(lat,
+        lng,
+        radius,
+        unit,
+        geoHashField,
+        tierFieldPrefix,
+        distanceCalculator,
+        new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy()),
+        1,
+        query);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+    FilteredQuery filteredQuery = new FilteredQuery(query, cartesianFilter);
+    QueryWrapperFilter filter = new QueryWrapperFilter(filteredQuery);
+
+    DocIdSet docIdSet = filter.getDocIdSet(reader);
+
+    BitSet bitSet = new BitSet();
+    int docId;
+    for (DocIdSetIterator iterator = docIdSet.iterator(); (docId = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS;) {
+      bitSet.set(docId);
+    }
+
+    return new DocIdBitSet(distanceFilter.bits(reader, bitSet));
+  }
+
+  // =============================================== Getters / Setters ===============================================
+
+  /**
+   * Returns the instantiated {@link org.apache.lucene.spatial.distance.DistanceFilter}.
+   *
+   * @return Instantiated DistanceFilter.  Will always be non-null.
+   */
+  public DistanceFilter getDistanceFilter() {
+    return distanceFilter;
+  }
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/TestDistanceFieldComparatorSource.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/TestDistanceFieldComparatorSource.java	Sun Dec 20 21:12:54 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/TestDistanceFieldComparatorSource.java	Sun Dec 20 21:12:54 CET 2009
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.spatial;
+
+import junit.framework.TestCase;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.spatial.distance.DistanceFilter;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.DistanceFieldComparatorSource}
+ */
+public class TestDistanceFieldComparatorSource extends TestCase {
+
+  public void testNewComparator_lessThan() throws Exception {
+    Map<Integer, Double> distances = new HashMap<Integer, Double>();
+    distances.put(1, 0.1);
+    distances.put(2, 0.2);
+
+    DistanceFilter distanceFilter = new StubDistanceFilter(distances);
+    DistanceFieldComparatorSource comparatorSource = new DistanceFieldComparatorSource(distanceFilter);
+
+    FieldComparator fieldComparator = comparatorSource.newComparator("field", 3, 0, false);
+    fieldComparator.copy(0, 1);
+    fieldComparator.copy(1, 2);
+    assertEquals(-1, fieldComparator.compare(0, 1));
+  }
+
+  public void testNewComparator_greaterThan() throws Exception {
+    Map<Integer, Double> distances = new HashMap<Integer, Double>();
+    distances.put(1, 0.2);
+    distances.put(2, 0.1);
+
+    DistanceFilter distanceFilter = new StubDistanceFilter(distances);
+    DistanceFieldComparatorSource comparatorSource = new DistanceFieldComparatorSource(distanceFilter);
+
+    FieldComparator fieldComparator = comparatorSource.newComparator("field", 3, 0, false);
+    fieldComparator.copy(0, 1);
+    fieldComparator.copy(1, 2);
+    assertEquals(1, fieldComparator.compare(0, 1));
+  }
+
+  public void testNewComparator_equal() throws Exception {
+    Map<Integer, Double> distances = new HashMap<Integer, Double>();
+    distances.put(1, 0.2);
+    distances.put(2, 0.2);
+
+    DistanceFilter distanceFilter = new StubDistanceFilter(distances);
+    DistanceFieldComparatorSource comparatorSource = new DistanceFieldComparatorSource(distanceFilter);
+
+    FieldComparator fieldComparator = comparatorSource.newComparator("field", 3, 0, false);
+    fieldComparator.copy(0, 1);
+    fieldComparator.copy(1, 2);
+    assertEquals(0, fieldComparator.compare(0, 1));
+  }
+
+  // =============================================== Inner Classes ===================================================
+
+  private static class StubDistanceFilter implements DistanceFilter {
+
+    private Map<Integer, Double> distances;
+
+    private StubDistanceFilter(Map<Integer, Double> distances) {
+      this.distances = distances;
+    }
+
+    public Map<Integer, Double> getDistances() {
+      return distances;
+    }
+
+    public Double getDistance(int docId) {
+      return distances.get(docId);
+    }
+
+    public BitSet bits(IndexReader reader, BitSet bits) throws IOException {
+      return bits;
+    }
+  }
+
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java	(revision 833867)
+++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java	Sun Dec 20 21:32:43 CET 2009
@@ -50,8 +50,11 @@
 import org.apache.lucene.util.NumericUtils;
 
 /**
- *
+ * @deprecated This test class tests primarly tests deprecated behavior.  It has been replaced by
+ *             {@link org.apache.lucene.spatial.TestSpatialFilter} which tests the non-deprecated spatial filtering process.
+ *             This will be removed.
  */
+@Deprecated
 public class TestCartesian extends TestCase{
 
   /**
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFieldComparatorSource.java	Sun Dec 20 21:28:15 CET 2009
@@ -28,7 +28,11 @@
  * <p><font color="red"><b>NOTE:</b> This API is still in
  * flux and might change in incompatible ways in the next
  * release.</font>
+ *
+ * @deprecated This class is deprecated in favour of {@link org.apache.lucene.spatial.DistanceFieldComparatorSource} which
+ *             uses instances {@link org.apache.lucene.spatial.distance.DistanceFilter}.  This will be removed
  */
+@Deprecated
 public class DistanceFieldComparatorSource extends FieldComparatorSource {
 
 	private static final long serialVersionUID = 1L;
Index: contrib/spatial/src/test/org/apache/lucene/spatial/TestSpatialFilter.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/TestSpatialFilter.java	Sun Dec 20 21:23:53 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/TestSpatialFilter.java	Sun Dec 20 21:23:53 CET 2009
@@ -0,0 +1,405 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.spatial;
+
+import junit.framework.TestCase;
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.*;
+import org.apache.lucene.search.function.CustomScoreQuery;
+import org.apache.lucene.search.function.FieldScoreQuery;
+import org.apache.lucene.search.function.FieldScoreQuery.Type;
+import org.apache.lucene.search.function.ValueSourceQuery;
+import org.apache.lucene.spatial.geohash.GeoHashUtils;
+import org.apache.lucene.spatial.geometry.ArcGeoDistanceCalculator;
+import org.apache.lucene.spatial.geometry.DistanceUnits;
+import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter;
+import org.apache.lucene.spatial.tier.projections.IProjector;
+import org.apache.lucene.spatial.tier.projections.SinusoidalProjector;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.NumericUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.SpatialFilter}.  Tests the full filtering functionality
+ */
+public class TestSpatialFilter extends TestCase {
+
+  private static final String TIER_FIELD_PREFIX = "_tier_";
+
+  private Directory directory;
+  private IndexSearcher searcher;
+  
+  private String latField = "lat";
+  private String lngField = "lng";
+  private String geoHashField = "_geoHash_";
+
+  private List<CartesianTierPlotter> cartesianTierPlotters;
+  private IProjector projector = new SinusoidalProjector();
+
+  @Override
+  protected void setUp() throws IOException {
+    directory = new RAMDirectory();
+    cartesianTierPlotters = createPlotters(2, 15);
+    IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.UNLIMITED);
+    addData(writer);
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    searcher.close();
+    directory.close();
+  }
+
+  public void testAntiM() throws IOException {
+    searcher = new IndexSearcher(directory, true);
+
+    double miles = 2800.0;
+    // Hawaii - 2300 miles to Marshall Island Airfield
+    double lat = 21.6032207;
+    double lng = -158.0;
+
+    // Create a term query to search against all documents
+    Query termQuery = new TermQuery(new Term("metafile", "doc"));
+
+    SpatialFilter spatialFilter = new SpatialFilter(
+        lat,
+        lng,
+        miles,
+        DistanceUnits.MILES,
+        latField,
+        lngField,
+        TIER_FIELD_PREFIX,
+        new ArcGeoDistanceCalculator(),
+        termQuery);
+
+    FieldScoreQuery fieldScoreQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+    CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fieldScoreQuery, spatialFilter, miles);
+
+    // Create a distance sort
+    // As the radius filter has performed the distance calculations already, pass in the filter to reuse the results.
+    DistanceFieldComparatorSource comparatorSource = new DistanceFieldComparatorSource(spatialFilter.getDistanceFilter());
+    Sort sort = new Sort(new SortField("foo", comparatorSource, false));
+
+    // Perform the search, using the term query, the serial chain filter, and the distance sort
+    TopDocs hits = searcher.search(customScoreQuery.createWeight(searcher), spatialFilter, 1000, sort);
+    int results = hits.totalHits;
+    ScoreDoc[] scoreDocs = hits.scoreDocs;
+
+    Map<Integer, Double> distances = spatialFilter.getDistanceFilter().getDistances();
+
+    // distances calculated from filter first pass must be less than total docs, from the above test of 20 items,
+    // 12 will come from the boundary box filter, but only 5 are actually in the radius of the results.
+    // Note Boundary Box filtering, is not accurate enough for most systems.
+
+    assertEquals(2, distances.size());
+    assertEquals(2, results);
+
+    double lastDistance = 0;
+    for(int i = 0 ; i < results; i++) {
+      Double geoDistance = distances.get(scoreDocs[i].doc);
+
+      assertTrue(geoDistance < miles);
+      assertTrue(geoDistance >= lastDistance);
+      lastDistance = geoDistance;
+    }
+  }
+
+  public void testPoleFlipping() throws IOException {
+    searcher = new IndexSearcher(directory, true);
+
+    double miles = 3500.0;
+    double lat = 41.6032207;
+    double lng = -73.087749;
+
+    //create a term query to search against all documents
+    Query termQuery = new TermQuery(new Term("metafile", "doc"));
+
+    SpatialFilter spatialFilter = new SpatialFilter(
+        lat,
+        lng,
+        miles,
+        DistanceUnits.MILES,
+        latField,
+        lngField,
+        TIER_FIELD_PREFIX,
+        new ArcGeoDistanceCalculator(),
+        termQuery);
+
+    FieldScoreQuery fieldScoreQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+    CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fieldScoreQuery, spatialFilter, miles);
+
+    // Create a distance sort
+    // As the radius filter has performed the distance calculations already, pass in the filter to reuse the results.
+    DistanceFieldComparatorSource comparatorSource = new DistanceFieldComparatorSource(spatialFilter.getDistanceFilter());
+    Sort sort = new Sort(new SortField("foo", comparatorSource, false));
+
+    // Perform the search, using the term query, the serial chain filter, and the distance sort
+    TopDocs hits = searcher.search(customScoreQuery.createWeight(searcher), spatialFilter, 1000, sort);
+    int results = hits.totalHits;
+    ScoreDoc[] scoreDocs = hits.scoreDocs;
+
+    Map<Integer, Double> distances = spatialFilter.getDistanceFilter().getDistances();
+
+    // distances calculated from filter first pass must be less than total docs, from the above test of 20 items, 12 will
+    // come from the boundary box filter, but only 5 are actually in the radius of the results.
+    // Note Boundary Box filtering, is not accurate enough for most systems.
+
+    assertEquals(18, distances.size());
+    assertEquals(18, results);
+
+    double lastDistance = 0;
+    for(int i = 0 ; i < results; i++){
+      Double geoDistance = distances.get(scoreDocs[i].doc);
+
+      assertTrue(geoDistance < miles);
+      assertTrue(geoDistance >= lastDistance);
+      lastDistance = geoDistance;
+    }
+  }
+
+  public void testRange() throws IOException {
+    searcher = new IndexSearcher(directory, true);
+
+    double lat = 38.969398;
+    double lng = -77.386398;
+
+    double[] milesToTest = new double[] {6.0, 0.5, 0.001, 0.0};
+    int[] expected = new int[] {7, 1, 0, 0};
+
+    for (int x = 0; x < expected.length; x++) {
+      double miles = milesToTest[x];
+
+      //create a term query to search against all documents
+      Query termQuery = new TermQuery(new Term("metafile", "doc"));
+
+      SpatialFilter spatialFilter = new SpatialFilter(
+          lat,
+          lng,
+          miles,
+          DistanceUnits.MILES,
+          latField,
+          lngField,
+          TIER_FIELD_PREFIX,
+          new ArcGeoDistanceCalculator(),
+          termQuery);
+
+      FieldScoreQuery fieldScoreQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+      CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fieldScoreQuery, spatialFilter, miles);
+
+      // Create a distance sort
+      // As the radius filter has performed the distance calculations already, pass in the filter to reuse the results.
+      DistanceFieldComparatorSource comparatorSource = new DistanceFieldComparatorSource(spatialFilter.getDistanceFilter());
+      Sort sort = new Sort(new SortField("foo", comparatorSource, false));
+
+      // Perform the search, using the term query, the serial chain filter, and the distance sort
+      TopDocs hits = searcher.search(customScoreQuery.createWeight(searcher), spatialFilter, 1000, sort);
+      int results = hits.totalHits;
+      ScoreDoc[] scoreDocs = hits.scoreDocs;
+
+      Map<Integer,Double> distances = spatialFilter.getDistanceFilter().getDistances();
+
+      // distances calculated from filter first pass must be less than total docs, from the above test of 20 items,
+      // 12 will come from the boundary box filter, but only 5 are actually in the radius of the results.
+      // Note Boundary Box filtering, is not accurate enough for most systems.
+
+      assertEquals(expected[x], distances.size());
+      assertEquals(expected[x], results);
+
+      double lastDistance = 0;
+      for(int i = 0 ; i < results; i++) {
+        Double geoDistance = distances.get(scoreDocs[i].doc);
+
+        assertTrue(geoDistance < miles);
+        assertTrue(geoDistance > lastDistance);
+        lastDistance = geoDistance;
+      }
+    }
+  }
+
+  public void testGeoHashRange() throws IOException {
+    searcher = new IndexSearcher(directory, true);
+
+    double lat = 38.969398;
+    double lng = -77.386398;
+
+    double[] milesToTest = new double[] {6.0, 0.5, 0.001, 0.0};
+    int[] expected = new int[] {7, 1, 0, 0};
+
+    for (int x = 0; x < expected.length; x++) {
+      double miles = milesToTest[x];
+
+      //create a term query to search against all documents
+      Query termQuery = new TermQuery(new Term("metafile", "doc"));
+      final SpatialFilter spatialFilter = new SpatialFilter(
+          lat,
+          lng,
+          miles,
+          DistanceUnits.MILES,
+          geoHashField,
+          TIER_FIELD_PREFIX,
+          new ArcGeoDistanceCalculator(),
+          termQuery);
+
+      FieldScoreQuery fieldScoreQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+      CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fieldScoreQuery, spatialFilter, miles);
+
+      // Perform the search, using the term query, the serial chain filter, and the distance sort
+      TopDocs hits = searcher.search(customScoreQuery.createWeight(searcher), spatialFilter, 1000);
+      int results = hits.totalHits;
+      ScoreDoc[] scoreDocs = hits.scoreDocs;
+
+      Map<Integer, Double> distances = spatialFilter.getDistanceFilter().getDistances();
+
+      // distances calculated from filter first pass must be less than total docs, from the above test of 20 items,
+      // 12 will come from the boundary box filter, but only 5 are actually in the radius of the results.
+      // Note Boundary Box filtering, is not accurate enough for most systems.
+
+      assertEquals(expected[x], distances.size());
+      assertEquals(expected[x], results);
+
+      for(int i = 0; i < results; i++) {
+        Double geoDistance = distances.get(scoreDocs[i].doc);
+        assertTrue(geoDistance < miles);
+      }
+    }
+  }
+
+  // ================================================= Helper Methods ================================================
+
+  /**
+   * Adds some test data to the index referenced by the given IndexWriter
+   *
+   * @param writer Writer to use to write to the index
+   * @throws IOException Can be thrown while writing to the index
+   */
+  private void addData(IndexWriter writer) throws IOException {
+    addPoint(writer, "McCormick &amp; Schmick's Seafood Restaurant", 38.9579000, -77.3572000);
+    addPoint(writer, "Jimmy's Old Town Tavern", 38.9690000, -77.3862000);
+    addPoint(writer, "Ned Devine's", 38.9510000, -77.4107000);
+    addPoint(writer, "Old Brogue Irish Pub", 38.9955000, -77.2884000);
+    addPoint(writer, "Alf Laylah Wa Laylah", 38.8956000, -77.4258000);
+    addPoint(writer, "Sully's Restaurant &amp; Supper", 38.9003000, -77.4467000);
+    addPoint(writer, "TGIFriday", 38.8725000, -77.3829000);
+    addPoint(writer, "Potomac Swing Dance Club", 38.9027000, -77.2639000);
+    addPoint(writer, "White Tiger Restaurant", 38.9027000 ,-77.2638000);
+    addPoint(writer, "Jammin' Java", 38.9039000, -77.2622000);
+    addPoint(writer, "Potomac Swing Dance Club", 38.9027000, -77.2639000);
+    addPoint(writer, "WiseAcres Comedy Club", 38.9248000, -77.2344000);
+    addPoint(writer, "Glen Echo Spanish Ballroom", 38.9691000, -77.1400000);
+    addPoint(writer, "Whitlow's on Wilson", 38.8889000, -77.0926000);
+    addPoint(writer, "Iota Club and Cafe", 38.8890000, -77.0923000);
+    addPoint(writer, "Hilton Washington Embassy Row", 38.9103000, -77.0451000);
+    addPoint(writer, "HorseFeathers, Bar & Grill", 39.01220000000001, -77.3942);
+    addPoint(writer, "Marshall Island Airfield", 7.06, 171.2);
+    addPoint(writer, "Midway Island", 25.7, -171.7);
+    addPoint(writer, "North Pole Way", 55.0, 4.0);
+
+    writer.commit();
+    writer.close();
+  }
+
+    /**
+   * Creates a document representing the point with given latitude and longitude, and adds it to the index via the given
+   * IndexWriter
+   *
+   * @param writer IndexWriter to use to write to the index
+   * @param name Name information to add to the document.  Can be retrieved from the document through the field "name"
+   * @param lat Latitude of the point
+   * @param lng Longitude of the point
+   * @throws IOException Can be thrown while writing to the index
+   */
+  private void addPoint(IndexWriter writer, String name, double lat, double lng) throws IOException {
+    Document doc = new Document();
+    doc.add(new Field("name", name,Field.Store.YES, Field.Index.ANALYZED));
+
+    // convert the lat / long to lucene fields
+    doc.add(new Field(latField, NumericUtils.doubleToPrefixCoded(lat),Field.Store.YES, Field.Index.NOT_ANALYZED));
+    doc.add(new Field(lngField, NumericUtils.doubleToPrefixCoded(lng),Field.Store.YES, Field.Index.NOT_ANALYZED));
+
+    // add a default meta field to make searching all documents easy
+    doc.add(new Field("metafile", "doc",Field.Store.YES, Field.Index.ANALYZED));
+
+    for (CartesianTierPlotter tierPlotter : cartesianTierPlotters) {
+      doc.add(new Field(TIER_FIELD_PREFIX + tierPlotter.getTierLevelId(),
+          NumericUtils.doubleToPrefixCoded(tierPlotter.getTierBoxId(lat,lng)),
+          Field.Store.YES,
+          Field.Index.NOT_ANALYZED_NO_NORMS));
+    }
+
+    doc.add(new Field(geoHashField, GeoHashUtils.encode(lat, lng), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
+    writer.addDocument(doc);
+  }
+
+  /**
+   * Creates a list of CartesianTierPlotters from the given base tier id to the given top tier id
+   *
+   * @param base Tier ID of the base plotter
+   * @param top Tier ID of the top plotter
+   * @return List of CartesianTierPlotters from the base tier to the top tier
+   */
+  private List<CartesianTierPlotter> createPlotters(int base, int top) {
+    List<CartesianTierPlotter> tierPlotters = new ArrayList<CartesianTierPlotter>();
+    for (; base <= top; base++){
+      tierPlotters.add(new CartesianTierPlotter(base, projector));
+    }
+    return tierPlotters;
+  }
+
+  // ================================================= Inner Classes =================================================
+
+  /**
+   * CustomScoreQuery that boosts the score of documents so that those closer to the centre of the search are boosted higher
+   */
+  private static class DistanceCustomScoreQuery extends CustomScoreQuery {
+
+    private final SpatialFilter spatialFilter;
+    private final double miles;
+
+    private DistanceCustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery, SpatialFilter spatialFilter, double miles) {
+      super(subQuery, valSrcQuery);
+      this.spatialFilter = spatialFilter;
+      this.miles = miles;
+    }
+
+    @Override
+    public float customScore(int doc, float subQueryScore, float valSrcScore) {
+      Double docDistance = spatialFilter.getDistanceFilter().getDistance(doc);
+      if (docDistance == null) {
+        return 0;
+      }
+
+      // boost score shouldn't exceed 1
+      if (docDistance < 1.0) {
+        docDistance = 1.0;
+      }
+
+      // boost by distance is invertly proportional to to distance from center point to location
+      float score = (float) ((miles - docDistance) / miles);
+      return score * subQueryScore;
+    }
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQueryBuilder.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQueryBuilder.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQueryBuilder.java	Sun Dec 20 21:29:03 CET 2009
@@ -28,7 +28,11 @@
  * <p><font color="red"><b>NOTE:</b> This API is still in
  * flux and might change in incompatible ways in the next
  * release.</font>
+ *
+ * @deprecated This class is deprecated in favour of {@link org.apache.lucene.spatial.SpatialFilter} as the preferred
+ *             entry point to the spatial filtering process.  This will be removed.
  */
+@Deprecated
 public class DistanceQueryBuilder {
 
   private static final long serialVersionUID = 1L;
Index: contrib/spatial/src/java/org/apache/lucene/spatial/DistanceFieldComparatorSource.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/DistanceFieldComparatorSource.java	Sun Dec 20 21:11:57 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/DistanceFieldComparatorSource.java	Sun Dec 20 21:11:57 CET 2009
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.spatial;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.spatial.distance.DistanceFilter;
+
+import java.io.IOException;
+
+/**
+ * DistanceFieldComparatorSource is reponsible for creating fieldcomparators for sorting on distance.
+ *
+ * <p><font color="red"><b>NOTE:</b> This API is still in flux and might change in incompatible ways in the next release.</font>
+ */
+public class DistanceFieldComparatorSource extends FieldComparatorSource {
+
+  private DistanceFilter distanceFilter;
+  private DistanceComparator comparator;
+
+  /**
+   * Constructs a DistanceFieldComparatorSource with the specified distanceFilter.
+   * The distanceFilter is used as a source for all the document distances from a central point.
+   *
+   * @param distanceFilter The specified distanceFiter. Cannot be {@code null}
+   */
+  public DistanceFieldComparatorSource(DistanceFilter distanceFilter) {
+    if (distanceFilter == null) {
+      throw new IllegalArgumentException("distanceFilter cannot be null");
+    }
+    this.distanceFilter = distanceFilter;
+  }
+
+  /**
+   * Cleans up any resource associated with this field compararor source.
+   */
+  public void cleanUp() {
+    distanceFilter = null;
+
+    if (comparator != null) {
+      comparator.cleanUp();
+      comparator = null;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
+    comparator = new DistanceComparator(distanceFilter, numHits);
+    return comparator;
+  }
+
+
+  // ============================================ Inner Classes ======================================================
+
+  /**
+   * FieldComparator that uses the calculated distances to sort documents
+   */
+  private final class DistanceComparator extends FieldComparator {
+
+    private DistanceFilter distanceFilter;
+    private double[] values;
+    private double bottom;
+    private int offset = 0;
+
+    /**
+     * Creates a new DistanceComparator which will use the distances calculated by the given DistanceFilter
+     *
+     * @param distanceFilter DistanceFilter that holds the calculated distances for the documents
+     * @param numHits Number of hits that will be sorted by the comparator
+     */
+    public DistanceComparator(DistanceFilter distanceFilter, int numHits) {
+      this.distanceFilter = distanceFilter;
+      this.values = new double[numHits];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compare(int slot1, int slot2) {
+      double a = values[slot1];
+      double b = values[slot2];
+      if (a > b) {
+        return 1;
+      } else if (a < b) {
+        return -1;
+      }
+
+      return 0;
+    }
+
+    /**
+     * Cleans up the FieldComparator by releasing the reference to the DistanceFilter
+     */
+    public void cleanUp() {
+      distanceFilter = null;
+      values = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareBottom(int doc) {
+      double v2 = distanceFilter.getDistance(doc + offset);
+
+      if (bottom > v2) {
+        return 1;
+      } else if (bottom < v2) {
+        return -1;
+      }
+      return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void copy(int slot, int doc) {
+      values[slot] = distanceFilter.getDistance(doc + offset);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setBottom(int slot) {
+      this.bottom = values[slot];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      // each reader in a segmented base has an offset based on the maxDocs of previous readers
+      offset = docBase;
+		}
+
+    /**
+     * {@inheritDoc}
+     */
+    public Comparable<Double> value(int slot) {
+      return values[slot];
+		}
+	}
+
+}
