Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/ArcDistanceCalculator.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/ArcDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/ArcDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,42 @@
+/**
+ * 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.geometry;
+
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+/**
+ * Implementation of {@link GeoDistanceCalculator} that calculates distances as though the points are on a globe.
+ *
+ * <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 ArcDistanceCalculator implements GeoDistanceCalculator {
+
+  /**
+   * {@inheritDoc}
+   */
+  public double calculate(
+      double sourceLatitude,
+      double sourceLongitude,
+      double targetLatitude,
+      double targetLongitude,
+      DistanceUnit unit) {
+    LatLng sourcePoint = new LatLng(sourceLatitude, sourceLongitude);
+    LatLng targetPoint = new LatLng(targetLatitude, targetLongitude);
+    return DistanceUnit.convert(sourcePoint.arcDistance(targetPoint, DistanceUnit.MILES), DistanceUnit.MILES, unit);
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/distance/ThreadedDistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/distance/ThreadedDistanceFilter.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/ThreadedDistanceFilter.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,235 @@
+/**
+ * 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.distance;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.spatial.geometry.GeoDistanceCalculator;
+import org.apache.lucene.spatial.geometry.Point;
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+/**
+ * Implementation of {@link DistanceFilter} that uses multiple threads to iterate in
+ * parallel, over the BitSet to be filtered.
+ * <p/>
+ * To manage the threads, an ExecutorService is used, which allows users of this class to have fine grained control over
+ * how many threads should be created, and what to do when there isn't any threads left in the pool.
+ *
+ * <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 ThreadedDistanceFilter implements DistanceFilter {
+
+  private final List<Map<Integer, Double>> distanceMaps = new ArrayList<Map<Integer, Double>>();
+  private final double lat;
+  private final double lng;
+  private final double radius;
+  private final DistanceUnit unit;
+
+  private final GeoDistanceCalculator distanceCalculator;
+  private final LocationDataSetFactory dataSetFactory;
+
+  private final ExecutorService executorService;
+  private final int threadCount;
+
+  private int nextOffset = 0;
+
+  /**
+   * Creates a new ThreadedDistanceFilter that will filter out documents that are outside of the given radius of 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 that documents must be within from the central point to pass the filter
+   * @param unit Unit of distance the radius is in
+   * @param dataSetFactory LocationDataSetFactory which can be used to create LocationDataSets from an IndexReader
+   * @param distanceCalculator GeoDistanceCalculator that will be used to calculate the distances between points
+   * @param executorService ExecutorService which will manage the execution of the threads
+   * @param threadCount Number of threads that the filter should try to split its work across
+   */
+  public ThreadedDistanceFilter(
+      double lat,
+      double lng,
+      double radius,
+      DistanceUnit unit,
+      LocationDataSetFactory dataSetFactory,
+      GeoDistanceCalculator distanceCalculator,
+      ExecutorService executorService,
+      int threadCount) {
+
+    this.lat = lat;
+    this.lng = lng;
+    this.radius = radius;
+    this.unit = unit;
+    this.distanceCalculator = distanceCalculator;
+    this.dataSetFactory = dataSetFactory;
+    this.executorService = executorService;
+    this.threadCount = threadCount;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<Integer, Double> getDistances() {
+    Map<Integer, Double> combinedDistances = new HashMap<Integer, Double>();
+    for (Map<Integer, Double> distanceMap : distanceMaps) {
+      combinedDistances.putAll(distanceMap);
+    }
+    return combinedDistances;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Double getDistance(int docId) {
+    for (Map<Integer, Double> distanceMap : distanceMaps) {
+      Double distance = distanceMap.get(docId);
+      if (distance != null) {
+        return distance;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public BitSet bits(final IndexReader reader, final BitSet bits) throws IOException {
+    final LocationDataSet dataSet = dataSetFactory.buildLocationDataSet(reader);
+
+    int maxLength = bits.length();
+    int threadSize = maxLength / threadCount;
+    List<Callable<IterationResult>> tasks = new ArrayList<Callable<IterationResult>>();
+
+    for (int i = 0; i < threadCount; i++) {
+      final int start = i * threadSize;
+      // if the last batch of documents has been reached, then maxLength should be end
+      final int end = (i == threadCount - 1) ? maxLength : Math.min((i + 1) * threadSize, maxLength);
+      tasks.add(new Callable<IterationResult>() {
+        public IterationResult call() throws Exception {
+          return iterate(dataSet, bits, start, end, end - start, reader);
+        }
+      });
+    }
+
+    BitSet result = new BitSet(bits.cardinality());
+
+    try {
+      List<Future<IterationResult>> results = executorService.invokeAll(tasks);
+      for (Future<IterationResult> resultFuture : results) {
+        IterationResult iterationResult = resultFuture.get();
+        result.or(iterationResult.getBitSet());
+        distanceMaps.add(iterationResult.getDistanceById());
+      }
+    } catch (InterruptedException ie) {
+      throw new RuntimeException("InterruptedException thrown while executing tasks", ie);
+    } catch (ExecutionException ee) {
+      throw new RuntimeException("ExecutionException thrown while retrieving results of tasks", ee);
+    }
+
+    nextOffset += reader.maxDoc();
+
+    return result;
+  }
+
+  // ================================================ Helper Methods =================================================
+
+  /**
+   * Iterates over the set bits in the given BitSet from the given start to end range, calculating the distance of the
+   * documents and determining which are within the distance radius of the central point.
+   *
+   * @param dataSet LocationDataSet containing the document locations that can be used to calculate the distance each
+   * document is from the central point
+   * @param originalBitSet BitSet which has bits set identifying which documents should be checked to see if their
+   * distance falls within the radius
+   * @param start Index in the BitSet that the method will start at
+   * @param end Index in the BitSet that the method will stop at
+   * @param size Size the the resulting BitSet should be created at (most likely end - start)
+   * @param reader IndexReader for checking if the document has been deleted
+   * @return IterationResult containing all the results of the method.
+   */
+  protected IterationResult iterate(LocationDataSet dataSet, BitSet originalBitSet, int start, int end, int size, IndexReader reader) {
+    BitSet bitSet = new BitSet(size);
+
+    Map<Integer, Double> distanceById = new HashMap<Integer, Double>();
+
+    int docId = originalBitSet.nextSetBit(start);
+    while (docId != -1 && docId < end) {
+      if (reader.isDeleted(docId)) {
+        docId = originalBitSet.nextSetBit(docId + 1);
+        continue;
+      }
+
+      Point point = dataSet.getPoint(docId);
+      double distance = distanceCalculator.calculate(lat, lng, point.getX(), point.getY(), unit);
+      if (distance < radius) {
+        bitSet.set(docId);
+        distanceById.put(docId + nextOffset, distance);
+      }
+
+      docId = originalBitSet.nextSetBit(docId + 1);
+    }
+    return new IterationResult(bitSet, distanceById);
+  }
+
+  // ================================================= Inner Classes =================================================
+
+  /**
+   * Wrapper of the results from {@link ThreadedDistanceFilter#iterate(LocationDataSet, BitSet, int, int, int, IndexReader)}.
+   * This allows the method to operate in almost total isolation in separate threads.
+   */
+  protected class IterationResult {
+
+    private BitSet bitSet;
+    private Map<Integer, Double> distanceById;
+
+    /**
+     * Creates a new IterationResult that wraps the given BitSet
+     *
+     * @param bitSet BitSet to wrap
+     * @param distanceById Document IDs with their calculated distances
+     */
+    public IterationResult(BitSet bitSet, Map<Integer, Double> distanceById) {
+      this.bitSet = bitSet;
+      this.distanceById = distanceById;
+    }
+
+    /**
+     * Returns the wrapped BitSet
+     *
+     * @return Wrapped BitSet
+     */
+    public BitSet getBitSet() {
+      return bitSet;
+    }
+
+    /**
+     * Returns the document IDs and calculated distances contained in this result
+     *
+     * @return Document IDs and calculated distances contained in this result
+     */
+    public Map<Integer, Double> getDistanceById() {
+      return distanceById;
+    }
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java	(revision 821186)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java	(revision 821186)
@@ -1,164 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.tier.projections;
-
-/**
- * <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 CartesianTierPlotter {
-  public static final String DEFALT_FIELD_PREFIX = "_tier_";
-  
-  final int tierLevel;
-  int tierLength;
-  int tierBoxes;
-  int tierVerticalPosDivider;
-  final IProjector projector;
-  final String fieldPrefix;
-  Double idd = Double.valueOf(180);
-  
-  public CartesianTierPlotter (int tierLevel, IProjector projector, String fieldPrefix) {
-  
-    this.tierLevel  = tierLevel;
-    this.projector = projector;
-    this.fieldPrefix = fieldPrefix;
-    
-    setTierLength();
-    setTierBoxes();
-    setTierVerticalPosDivider();
-  }
-  
-  private void setTierLength (){
-    this.tierLength = (int) Math.pow(2 , this.tierLevel);
-  }
-  
-  private void setTierBoxes () {
-    this.tierBoxes = (int)Math.pow(this.tierLength, 2);
-  }
-  
-  /**
-   * Get nearest max power of 10 greater than
-   * the tierlen
-   * e.g
-   * tierId of 13 has tierLen 8192
-   * nearest max power of 10 greater than tierLen 
-   * would be 10,000
-   */
-  
-  private void setTierVerticalPosDivider() {
-    
-    // ceiling of log base 10 of tierLen
-    
-    tierVerticalPosDivider = Double.valueOf(Math.ceil(
-          Math.log10(Integer.valueOf(this.tierLength).doubleValue()))).intValue();
-    
-    // 
-    tierVerticalPosDivider = (int)Math.pow(10, tierVerticalPosDivider );
-    
-  }
-  
-  public double getTierVerticalPosDivider(){
-    return tierVerticalPosDivider;
-  }
-  
-  /**
-   * TierBoxId is latitude box id + longitude box id
-   * where latitude box id, and longitude box id are transposed in to position
-   * coordinates.
-   * 
-   * @param latitude
-   * @param longitude
-   */
-  public double getTierBoxId (double latitude, double longitude) {
-    
-    double[] coords = projector.coords(latitude, longitude);
-    
-    double id = getBoxId(coords[0]) + (getBoxId(coords[1]) / tierVerticalPosDivider);
-    return id ;
-  }
-  
-  
-  private double getBoxId (double coord){
-    
-    
-    return Math.floor(coord / (idd / this.tierLength));
-  }
-  
-  @SuppressWarnings("unused")
-  private double getBoxId (double coord, int tierLen){
-    return Math.floor(coord / (idd / tierLen) );
-  }
-  /**
-   * get the string name representing current tier
-   * _localTier&lt;tiedId&gt;
-   */
-  public String getTierFieldName (){
-    
-    return fieldPrefix + this.tierLevel;
-  }
-  
-  /**
-   * get the string name representing tierId
-   * _localTier&lt;tierId&gt;
-   * @param tierId
-   */
-  public String getTierFieldName (int tierId){
-    
-    return fieldPrefix + tierId;
-  }
-  
-  /**
-   * Find the tier with the best fit for a bounding box
-   * Best fit is defined as the ceiling of
-   *  log2 (circumference of earth / distance) 
-   *  distance is defined as the smallest box fitting
-   *  the corner between a radius and a bounding box.
-   *  
-   *  Distances less than a mile return 15, finer granularity is
-   *  in accurate
-   */
-  public int bestFit(double miles){
-    
-    //28,892 a rough circumference of the earth
-    int circ = 28892;
-    
-    double r = miles / 2.0;
-    
-    double corner = r - Math.sqrt(Math.pow(r, 2) / 2.0d);
-    double times = circ / corner;
-    int bestFit =  (int)Math.ceil(log2(times)) + 1;
-    
-    if (bestFit > 15) {
-      // 15 is the granularity of about 1 mile
-      // finer granularity isn't accurate with standard java math
-      return 15;
-    }
-    return bestFit;
-  }
-  
-  /**
-   * a log to the base 2 formula
-   * <code>Math.log(value) / Math.log(2)</code>
-   * @param value
-   */
-  public double log2(double value) {
-    
-    return Math.log(value) / Math.log(2);
-  }
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java	(revision 825022)
+++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java	(revision 825022)
@@ -1,143 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.spatial.tier;
-
-import java.io.IOException;
-
-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.index.IndexReader;
-import org.apache.lucene.search.QueryWrapperFilter;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.util.NumericUtils;
-import org.apache.lucene.store.RAMDirectory;
-
-
-/**
- *
- */
-public class TestDistance extends TestCase{
-
-  
-  private RAMDirectory directory;
-  // reston va
-  private double lat = 38.969398; 
-  private double lng= -77.386398;
-  private String latField = "lat";
-  private String lngField = "lng";
-  private IndexWriter writer;
-  
-  @Override
-  protected void setUp() throws IOException {
-    directory = new RAMDirectory();
-    writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.UNLIMITED);
-    addData(writer);
-    
-  }
-
-  @Override
-  protected void tearDown() throws IOException {
-    writer.close();
-  }
-  
-  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));
-    writer.addDocument(doc);
-    
-  }
-  
-
-  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);
-    writer.commit();
-  }
-
-  public void testLatLongFilterOnDeletedDocs() throws Exception {
-    writer.deleteDocuments(new Term("name", "Potomac"));
-    IndexReader r = writer.getReader();
-    LatLongDistanceFilter f = new LatLongDistanceFilter(new QueryWrapperFilter(new MatchAllDocsQuery()),
-                                                        lat, lng, 1.0, latField, lngField);
-
-    IndexReader[] readers = r.getSequentialSubReaders();
-    for(int i=0;i<readers.length;i++) {
-      f.getDocIdSet(readers[i]);
-    }
-  }
- 
-  
-  public void testMiles() {
-    double LLM = DistanceUtils.getInstance().getLLMDistance(lat, lng,39.012200001, -77.3942);
-    System.out.println(LLM);
-    System.out.println("-->"+DistanceUtils.getInstance().getDistanceMi(lat, lng, 39.0122, -77.3942));
-  }
-  
-  public void testMiles2(){
-    System.out.println("Test Miles 2");
-    double LLM = DistanceUtils.getInstance().getLLMDistance(44.30073, -78.32131,43.687267, -79.39842);
-    System.out.println(LLM);
-    System.out.println("-->"+DistanceUtils.getInstance().getDistanceMi(44.30073, -78.32131, 43.687267, -79.39842));
-    
-  }
-
-  
-//  public void testDistanceQueryCacheable() throws IOException {
-//
-//    // create two of the same distance queries
-//    double miles = 6.0;
-//    DistanceQuery dq1 = new DistanceQuery(lat, lng, miles, latField, lngField, true);
-//    DistanceQuery dq2 = new DistanceQuery(lat, lng, miles, latField, lngField, true);
-//
-//    /* ensure that they hash to the same code, which will cause a cache hit in solr */
-//    System.out.println("hash differences?");
-//    assertEquals(dq1.getQuery().hashCode(), dq2.getQuery().hashCode());
-//    
-//    /* ensure that changing the radius makes a different hash code, creating a cache miss in solr */
-//    DistanceQuery widerQuery = new DistanceQuery(lat, lng, miles + 5.0, latField, lngField, false);
-//    assertTrue(dq1.getQuery().hashCode() != widerQuery.getQuery().hashCode());
-//  }
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/distance/TestThreadedDistanceFilter.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/distance/TestThreadedDistanceFilter.java	Wed Dec 09 18:22:09 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/distance/TestThreadedDistanceFilter.java	Wed Dec 09 18:22:09 CET 2009
@@ -0,0 +1,243 @@
+/**
+ * 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.distance;
+
+import junit.framework.TestCase;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.ParallelReader;
+import org.apache.lucene.spatial.geometry.ArcDistanceCalculator;
+import org.apache.lucene.spatial.geometry.GeoDistanceCalculator;
+import org.apache.lucene.spatial.geometry.Point;
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * Tests for {@link TestThreadedDistanceFilter}
+ */
+public class TestThreadedDistanceFilter extends TestCase {
+
+  private final LocationDataSet locationDataSet = new LocationDataSet() {
+
+    private List<Point> points = Arrays.asList(new Point(4.53, 30.61), new Point(4.51, 31.01), new Point(5.69, 40.89));
+
+    public Point getPoint(int docId) {
+      return points.get(docId);
+    }
+  };
+
+  public void testGeoCalculation_correctArgumentPassing() throws Exception {
+    double sourceLatitude = 1.0;
+    double sourceLongitude = 2.0;
+    final double targetLatitude = 3.0;
+    final double targetLongitude = 4.0;
+
+    LocationDataSetFactory dataSetFactory = new LocationDataSetFactory() {
+
+      public LocationDataSet buildLocationDataSet(IndexReader indexReader) throws IOException {
+        return new LocationDataSet() {
+
+          private List<Point> points = Arrays.asList(new Point(targetLatitude, targetLongitude));
+
+          public Point getPoint(int docId) {
+            return points.get(docId);
+          }
+        };
+      }
+    };
+
+    StubGeoDistanceCalculator distanceCalculator = new StubGeoDistanceCalculator();
+    ExecutorService executorService = Executors.newFixedThreadPool(1);
+
+    ThreadedDistanceFilter filter =
+        new ThreadedDistanceFilter(sourceLatitude, sourceLongitude, 10, DistanceUnit.KILOMETERS, dataSetFactory, distanceCalculator, executorService, 1);
+
+    IndexReader indexReader = new ParallelReader() {
+      @Override
+      public int maxDoc() {
+        return 0;
+      }
+    };
+    BitSet bitSet = new BitSet(1);
+    bitSet.set(0);
+    filter.bits(indexReader, bitSet);
+
+    assertEquals(sourceLatitude, distanceCalculator.getSourceLatitude(), 0);
+    assertEquals(sourceLongitude, distanceCalculator.getSourceLongitude(), 0);
+    assertEquals(targetLatitude, distanceCalculator.getTargetLatitude(), 0);
+    assertEquals(targetLongitude, distanceCalculator.getTargetLongitude(), 0);
+  }
+
+  /**
+   * Pass condition: 2 threads with the correct start and end indexes are created based on the BitSet given in the method
+   * call, and the number of threads that were requested.
+   *
+   * @throws java.io.IOException Can be thrown by the ParallelReader which is used as the IndexReader for the test
+   */
+  public void testBits() throws IOException {
+    LocationDataSetFactory dataSetFactory = new LocationDataSetFactory() {
+      public LocationDataSet buildLocationDataSet(IndexReader indexReader) throws IOException {
+        return locationDataSet;
+      }
+    };
+
+    ExecutorService executorService = new ThreadPoolExecutor(2, 6, 8, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+
+    BitSet bitSet = new BitSet(13);
+    bitSet.set(2, 6);
+    bitSet.set(8, 9);
+    bitSet.set(11);
+
+    final List<IterateCallInfo> infoList = Collections.synchronizedList(new ArrayList<IterateCallInfo>());
+    ThreadedDistanceFilter distanceFilter = new ThreadedDistanceFilter(4.52, 30.81, 30, DistanceUnit.MILES, dataSetFactory, new ArcDistanceCalculator(), executorService, 2) {
+
+      @Override
+      protected IterationResult iterate(LocationDataSet dataSet, BitSet originalBitSet, int start, int end, int size, IndexReader reader) {
+        infoList.add(new IterateCallInfo(start, end, size));
+        return new IterationResult(new BitSet(), Collections.EMPTY_MAP);
+      }
+    };
+
+    IndexReader indexReader = new ParallelReader() {
+      @Override
+      public int maxDoc() {
+        return 0;
+      }
+    };
+
+    distanceFilter.bits(indexReader, bitSet);
+
+    assertEquals(2, infoList.size());
+
+    Collections.sort(infoList, new Comparator<IterateCallInfo>() {
+      public int compare(IterateCallInfo info1, IterateCallInfo info2) {
+        return info1.getStart() - info2.getStart();
+      }
+    });
+
+    IterateCallInfo callInfo = infoList.get(0);
+    assertEquals(0, callInfo.getStart());
+    assertEquals(6, callInfo.getEnd());
+    assertEquals(6, callInfo.getSize());
+
+    callInfo = infoList.get(1);
+    assertEquals(6, callInfo.getStart());
+    assertEquals(12, callInfo.getEnd());
+    assertEquals(6, callInfo.getSize());
+  }
+
+  /**
+   * Pass condition: Of the 3 documents with points, 1 is outside of the radius and should be filtered out, 1 has been
+   * deleted so it should also be filtered out, leaving just 1 that passes the filter.  Its distance
+   * should be recorded in the calculated distances of the filter
+   *
+   * @throws IOException Can be thrown by the ParallelReader which is used as the IndexReader for the test
+   */
+  public void testIterate() throws IOException {
+    IndexReader indexReader = new ParallelReader() {
+      @Override
+      public boolean isDeleted(int n) {
+        return n == 1;
+      }
+    };
+
+    BitSet bitSet = new BitSet(2);
+    bitSet.set(0);
+    bitSet.set(1);
+    bitSet.set(2);
+
+    ThreadedDistanceFilter distanceFilter = new ThreadedDistanceFilter(4.52, 30.81, 30, DistanceUnit.MILES, null, new ArcDistanceCalculator(), null, 0);
+
+    ThreadedDistanceFilter.IterationResult result = distanceFilter.iterate(locationDataSet, bitSet, 0, 3, 3, indexReader);
+
+    assertNotNull(result);
+
+    BitSet resultingBitSet = result.getBitSet();
+    assertNotNull(resultingBitSet);
+    assertTrue(resultingBitSet.get(0));
+    assertFalse(resultingBitSet.get(1));
+    assertFalse(resultingBitSet.get(2));
+
+    assertNotNull(result.getDistanceById());
+    assertEquals(1, result.getDistanceById().size());
+  }
+
+  // ================================================= Inner Classes =================================================
+
+  private static class IterateCallInfo {
+
+    private final int start;
+    private final int end;
+    private final int size;
+
+    private IterateCallInfo(int start, int end, int size) {
+      this.start = start;
+      this.end = end;
+      this.size = size;
+    }
+
+    public int getStart() {
+      return start;
+    }
+
+    public int getEnd() {
+      return end;
+    }
+
+    public int getSize() {
+      return size;
+    }
+  }
+
+  /**
+   * Dummy implementation. Used for unit test to assure that the given arguments to a distance calculator are given correctly .
+   */
+  class StubGeoDistanceCalculator implements GeoDistanceCalculator {
+
+    private double sourceLatitude;
+    private double sourceLongitude;
+    private double targetLatitude;
+    private double targetLongitude;
+
+    public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
+      this.sourceLatitude = sourceLatitude;
+      this.sourceLongitude = sourceLongitude;
+      this.targetLatitude = targetLatitude;
+      this.targetLongitude = targetLongitude;
+      return 0.0;
+    }
+
+    public double getSourceLatitude() {
+      return sourceLatitude;
+    }
+
+    public double getSourceLongitude() {
+      return sourceLongitude;
+    }
+
+    public double getTargetLatitude() {
+      return targetLatitude;
+    }
+
+    public double getTargetLongitude() {
+      return targetLongitude;
+    }
+  }
+}
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	(revision 810951)
@@ -1,145 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.tier;
-
-import org.apache.lucene.search.ConstantScoreQuery;
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.QueryWrapperFilter;
-import org.apache.lucene.spatial.geohash.GeoHashDistanceFilter;
-import org.apache.lucene.misc.ChainedFilter;
-
-/**
- * <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 DistanceQueryBuilder {
-
-  private static final long serialVersionUID = 1L;
-  
-  private final double lat;
-  private final double lng;
-  private final double miles;
-  private final Filter filter;
-  final DistanceFilter distanceFilter;
-
-  /**
-   * Create a distance query using
-   * a boundary box wrapper around a more precise
-   * DistanceFilter.
-   * 
-   * @param lat
-   * @param lng
-   * @param miles
-   */
-  public DistanceQueryBuilder (double lat, double lng, double miles, 
-      String latField, String lngField, String tierFieldPrefix, boolean needPrecise) {
-
-    this.lat = lat;
-    this.lng = lng;
-    this.miles = miles;
-    
-    CartesianPolyFilterBuilder cpf = new CartesianPolyFilterBuilder(tierFieldPrefix);
-    Filter cartesianFilter = cpf.getBoundingArea(lat, lng, miles);
-
-    /* create precise distance filter */
-    if (needPrecise) {
-      filter = distanceFilter = new LatLongDistanceFilter(cartesianFilter, lat, lng, miles, latField, lngField);
-    } else {
-      filter = cartesianFilter;
-      distanceFilter = null;
-    }
-  }
-
-  /**
-   * Create a distance query using
-   * a boundary box wrapper around a more precise
-   * DistanceFilter.
-   * 
-   * @param lat
-   * @param lng
-   * @param miles
-   */
-  public DistanceQueryBuilder (double lat, double lng, double miles, 
-      String geoHashFieldPrefix, String tierFieldPrefix, boolean needPrecise){
-
-    this.lat = lat;
-    this.lng = lng;
-    this.miles = miles;
-    
-    CartesianPolyFilterBuilder cpf = new CartesianPolyFilterBuilder(tierFieldPrefix);
-    Filter cartesianFilter = cpf.getBoundingArea(lat, lng, miles);
-
-    /* create precise distance filter */
-    if (needPrecise) {
-      filter = distanceFilter = new GeoHashDistanceFilter(cartesianFilter, lat, lng, miles, geoHashFieldPrefix);
-    } else {
-      filter = cartesianFilter;
-      distanceFilter = null;
-    }
-  }
-
-  
-  /**
-  * Create a distance query using
-  * a boundary box wrapper around a more precise
-  * DistanceFilter.
-  */
-  public Filter getFilter() {
-    if (distanceFilter != null) {
-      distanceFilter.reset();
-    }
-    return filter;
-  }
-  
-  public Filter getFilter(Query query) {
-    // Chain the Query (as filter) with our distance filter
-    if (distanceFilter != null) {
-      distanceFilter.reset();
-    }
-    QueryWrapperFilter qf = new QueryWrapperFilter(query);
-    return new ChainedFilter(new Filter[] {qf, filter},
-                             ChainedFilter.AND);
-  }
-
-  public DistanceFilter getDistanceFilter() {
-    return distanceFilter;
-  }
-    
-  public Query getQuery(Query query){
-    return new ConstantScoreQuery(getFilter(query));
-  }
-  
-  public double getLat() {
-    return lat;
-  }
-
-  public double getLng() {
-    return lng;
-  }
-
-  public double getMiles() {
-    return miles;
-  }
-    
-  @Override
-  public String toString() {
-    return "DistanceQuery lat: " + lat + " lng: " + lng + " miles: "+ miles;
-  }
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestLatLng.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestLatLng.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestLatLng.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,43 @@
+/**
+ * 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.geometry;
+
+import junit.framework.TestCase;
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.geometry.LatLng}
+ */
+public class TestLatLng extends TestCase {
+
+  public void testDistances() {
+    LatLng p1 = new LatLng(7.06, 171.2);
+    LatLng p2 = new LatLng(21.6032207, -158.0);
+
+    double miles = p1.arcDistance(p2, DistanceUnit.MILES);
+
+    assertEquals(2288.8249593244245, miles);
+
+    LatLng p3 = new LatLng(41.6032207, -73.087749);
+    LatLng p4 = new LatLng(55.0, 4.0);
+
+    miles = p3.arcDistance(p4, DistanceUnit.MILES);
+
+    assertEquals(3474.331719998743, miles);
+  }
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesianShapeFilter.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesianShapeFilter.java	(revision 833122)
+++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesianShapeFilter.java	Wed Dec 09 16:23:54 CET 2009
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.lucene.spatial.tier;
 
 import java.io.ByteArrayOutputStream;
@@ -22,22 +23,21 @@
 import java.io.ObjectOutputStream;
 
 import junit.framework.TestCase;
+
-/**
+/** 
- * 
  * Test for {@link CartesianShapeFilter}
- *
  */
 public class TestCartesianShapeFilter extends TestCase {
 
   public void testSerializable() throws IOException {
-    CartesianShapeFilter filter = new CartesianShapeFilter(new Shape("1"),
-        "test");
+    CartesianShapeFilter filter = new CartesianShapeFilter(new CartesianShape(1), "test");
+
     try {
       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(bos);
       oos.writeObject(filter);
     } catch (NotSerializableException e) {
-      fail("Filter should be serializable but raised a NotSerializableException ["+e.getMessage()+"]");
+      fail("Filter should be serializable but raised a NotSerializableException [" + e.getMessage() + "]");
     }
   }
 
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/GeoDistanceCalculator.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/GeoDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/GeoDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,42 @@
+/**
+ * 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.geometry;
+
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+/**
+ * Abstraction of the idea of a calculator of distance between 2 points.  This is useful since while the world is curved,
+ * its not always necessary to calcuate the distance between 2 points as if they are on a curve.
+ *
+ * <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 interface GeoDistanceCalculator {
+
+  /**
+   * Calculates the distance between the point defined by the source latitude/longitude and the point defined by the
+   * target latitude/longitude
+   *
+   * @param sourceLongitude Longitude of the point the distance is being calculated from
+   * @param sourceLatitude Latitude of the point the distance is being calculated from
+   * @param targetLongitude Longitude of the point the distance is being calculated to
+   * @param targetLatitude Latitude of the point the distance is being calculated to
+   * @param unit Unit of distance the result should be returned in
+   * @return Distance between the 2 points in the unit of distance requested
+   */
+  double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit);
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/distance/GeoHashLocationDataSetFactory.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/distance/GeoHashLocationDataSetFactory.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/GeoHashLocationDataSetFactory.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,82 @@
+/**
+ * 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.distance;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.spatial.geometry.Point;
+import org.apache.lucene.spatial.util.GeoHashUtils;
+
+import java.io.IOException;
+
+/**
+ * Implementation of {@link LocationDataSetFactory} that supports location information
+ * being contained in a single geohashed field.
+ *
+ * @see org.apache.lucene.spatial.util.GeoHashUtils
+ *
+ * <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 GeoHashLocationDataSetFactory implements LocationDataSetFactory {
+
+  private final String geoHashField;
+
+  /**
+   * Creates a new GeoHashLocationDataSetFactory that will read from the field with the given name
+   *
+   * @param geoHashField Name of the field containing the geohashes
+   */
+  public GeoHashLocationDataSetFactory(String geoHashField) {
+    this.geoHashField = geoHashField;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public LocationDataSet buildLocationDataSet(IndexReader indexReader) throws IOException {
+    return new GeoHashLocationDataSet(FieldCache.DEFAULT.getStringIndex(indexReader, geoHashField));
+  }
+
+  // ================================================= Inner Classes =================================================
+
+  /**
+   * Implementation of LocationDataSet which uses a geohash stored in a single index field
+   */
+  private class GeoHashLocationDataSet implements LocationDataSet {
+
+    private FieldCache.StringIndex index;
+
+    /**
+     * Creates a new GeoHashLocationDataSet which uses the values in the given index
+     *
+     * @param index A StringIndex containing the values of the geohash field taken from the index
+     */
+    private GeoHashLocationDataSet(FieldCache.StringIndex index) {
+      this.index = index;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Point getPoint(int docId) {
+      String fieldValue = index.lookup[index.order[docId]];
+      double[] coords = GeoHashUtils.decode(fieldValue);
+      return new Point(coords[0], coords[1]);
+    }
+  }
+}
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/DistanceFieldComparatorSource.java	Wed Dec 09 21:21:55 CET 2009
@@ -1,129 +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.
- */
+    /**
+     * 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.tier;
+    package org.apache.lucene.spatial;
 
-import java.io.IOException;
-
-import org.apache.lucene.index.IndexReader;
+    import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.FieldComparator;
-import org.apache.lucene.search.FieldComparatorSource;
+    import org.apache.lucene.search.FieldComparator;
+    import org.apache.lucene.search.FieldComparatorSource;
+    import org.apache.lucene.spatial.distance.DistanceFilter;
 
+    import java.io.IOException;
+
-/**
+    /**
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+     * 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 {
+     */
+    public class DistanceFieldComparatorSource extends FieldComparatorSource {
 
-	private static final long serialVersionUID = 1L;
-
-	private DistanceFilter distanceFilter;
+      private DistanceFilter distanceFilter;
-	private DistanceScoreDocLookupComparator dsdlc;
+      private DistanceComparator comparator;
 
-	public DistanceFieldComparatorSource(Filter distanceFilter) {
-
-		this.distanceFilter = (DistanceFilter) distanceFilter;
-
+      /**
+       * 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;
+      public void cleanUp() {
+        distanceFilter = null;
 
-		if (dsdlc != null)
-			dsdlc.cleanUp();
-
-		dsdlc = null;
+        if (comparator != null) {
+          comparator.cleanUp();
+          comparator = null;
-	}
+        }
+      }
 
-	@Override
-	public FieldComparator newComparator(String fieldname, int numHits,
-			int sortPos, boolean reversed) throws IOException {
-		dsdlc = new DistanceScoreDocLookupComparator(distanceFilter, numHits);
-		return dsdlc;
+      /**
+       * {@inheritDoc}
+       */
+      public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException {
+        comparator = new DistanceComparator(distanceFilter, numHits);
+        return comparator;
-	}
+      }
 
-	private class DistanceScoreDocLookupComparator extends FieldComparator {
 
+      // ============================================ 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 DistanceFilter distanceFilter;
+        private double[] values;
+        private double bottom;
-		private int offset =0;
+        private int offset = 0;
-		
+
-		public DistanceScoreDocLookupComparator(DistanceFilter distanceFilter,
-				int numHits) {
+        /**
+         * 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.distanceFilter = distanceFilter;
-			values = new double[numHits];
-			return;
+          this.values = new double[numHits];
-		}
+        }
 
-		@Override
+        /**
+         * {@inheritDoc}
+         */
-		public int compare(int slot1, int slot2) {
-			double a = values[slot1];
-			double b = values[slot2];
+        public int compare(int slot1, int slot2) {
+          double a = values[slot1];
+          double b = values[slot2];
-			if (a > b)
+          if (a > b) {
-				return 1;
+            return 1;
-			if (a < b)
+          } else if (a < b) {
-				return -1;
+            return -1;
+          }
 
-			return 0;
-		}
+          return 0;
+        }
 
+        /**
+         * Cleans up the FieldComparator by releasing the reference to the DistanceFilter
+         */
-		public void cleanUp() {
-			distanceFilter = null;
+        public void cleanUp() {
+          distanceFilter = null;
+          values = null;
-		}
+        }
 
-		@Override
+        /**
+         * {@inheritDoc}
+         */
-		public int compareBottom(int doc) {
+        public int compareBottom(int doc) {
-			double v2 = distanceFilter.getDistance(doc+ offset);
+          double v2 = distanceFilter.getDistance(doc + offset);
-			
-			if (bottom > v2) {
-				return 1;
-			} else if (bottom < v2) {
-				return -1;
-			}
-			return 0;
-		}
+
+          if (bottom > v2) {
+            return 1;
+          } else if (bottom < v2) {
+            return -1;
+          }
+          return 0;
+        }
 
-		@Override
+        /**
+         * {@inheritDoc}
+         */
-		public void copy(int slot, int doc) {
-			values[slot] = distanceFilter.getDistance(doc + offset);
-		}
+        public void copy(int slot, int doc) {
+          values[slot] = distanceFilter.getDistance(doc + offset);
+        }
 
-		@Override
+        /**
+         * {@inheritDoc}
+         */
-		public void setBottom(int slot) {
-			this.bottom = values[slot];
+        public void setBottom(int slot) {
+          this.bottom = values[slot];
-
-		}
+        }
 
-		@Override
-                  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
+        /**
+         * {@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;
-		}
+          offset = docBase;
+            }
 
-		@Override
+        /**
+         * {@inheritDoc}
+         */
-		public Comparable<Double> value(int slot) {
-			return values[slot];
-		}
-	}
+        public Comparable<Double> value(int slot) {
+          return values[slot];
+            }
+        }
 
-}
+    }
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLngRectangle.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLngRectangle.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLngRectangle.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,155 @@
+/**
+ * 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.geometry;
+
+import org.apache.lucene.spatial.util.SpatialConstants;
+
+/**
+ * Rectangle based on latitude and longitudes
+ *
+ * <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 LatLngRectangle {
+
+  private final LatLng lowerLeft;
+  private final LatLng upperRight;
+
+  /**
+   * Creates a new LatLngRectangle with the given lowerLeft latitude/longitude and upperRight latitude/longitude
+   *
+   * @param lowerLeft Latitude & longitude of the lower left point of the Rectangle
+   * @param upperRight Latitude & longitude of the upper right point of the Rectangle
+   */
+  public LatLngRectangle(LatLng lowerLeft, LatLng upperRight) {
+    this.lowerLeft = lowerLeft;
+    this.upperRight = upperRight;
+  }
+
+  /**
+   * Creates a new LatLngRectangle with the given latitude/longitude as its centre, and the given width as both its width
+   * and height (creating a square box)
+   *
+   * @param centre Latitude and longitude of the centre of the box
+   * @param width Width/height of the box
+   * @return LatLngRectangle representing a box with the given centre and width
+   */
+  public static LatLngRectangle createBox(LatLng centre, double width) {
+    LatLng upperRight = boxCorner(centre, width, 45.0);
+    LatLng lowerLeft = boxCorner(centre, width, 225.0);
+    return new LatLngRectangle(lowerLeft, upperRight);
+  }
+
+  /**
+   * Converts the LatLngRectangle into a plain ordinary {@link org.apache.lucene.spatial.geometry.Rectangle}
+   *
+   * @return Rectangle representation of {@code this}
+   */
+  public Rectangle toRectangle() {
+    return new Rectangle(lowerLeft.getLng(), lowerLeft.getLng(), upperRight.getLng(), upperRight.getLat());
+  }
+
+  // =============================================== Helper methods ==================================================
+
+  /**
+   * Calculates the corner (defined by the given degrees) of a box with the given LatLng at its centre, the given widths
+   *
+   * @param centre LatLng that is the centre of the box
+   * @param width Width of the box
+   * @param degrees Degrees of the corner whose LatLng is to be calculated
+   * @return LatLng of the corner at the given degrees in the box
+   */
+  private static LatLng boxCorner(LatLng centre, double width, double degrees) {
+    double a = centre.getLat();
+    double b = centre.getLng();
+    double R = SpatialConstants.EARTH_RADIUS_MILES;
+    double brng = Math.PI * degrees / 180;
+    double lat1 = Math.PI * a / 180;
+    double lng1 = Math.PI * b / 180;
+
+    double lat2 = Math.asin(Math.sin(lat1) * Math.cos(width / R) + Math.cos(lat1) * Math.sin(width / R) * Math.cos(brng));
+    double lng2 = lng1 + Math.atan2(Math.sin(brng) * Math.sin(width / R) * Math.cos(lat1), Math.cos(width / R) - Math.sin(lat1) * Math.sin(lat2));
+
+    lat2 = (lat2 * 180) / Math.PI;
+    lng2 = (lng2 * 180) / Math.PI;
+
+    LatLng latLng = normLng(lat2, lng2);
+    return normLat(latLng.getLat(), latLng.getLng());
+  }
+
+  /**
+   * Normalises the given latitude and creates a new LatLng
+   *
+   * @param lat Latitude to normalise
+   * @param lng Longitude that will be part of the new LatLng
+   * @return LatLng created from the given lng and the normalisation of the lat
+   */
+  private static LatLng normLat(double lat, double lng) {
+    if (lat > 90.0) {
+      lat = 90.0 - (lat - 90.0);
+      if (lng < 0) {
+        lng = lng + 180;
+      } else {
+        lng = lng - 180;
+      }
+    } else if (lat < -90.0) {
+      lat = -90.0 - (lat + 90.0);
+      if (lng < 0) {
+        lng = lng + 180;
+      } else {
+        lng = lng - 180;
+      }
+    }
+    return new LatLng(lat, lng);
+  }
+
+  /**
+   * Normalises the given longitude and creates a new LatLng
+   *
+   * @param lat Latitude that will be part of the new LatLng
+   * @param lng Longitude to normalise
+   * @return LatLng created from the given lat and the normalisation of the lng
+   */
+  private static LatLng normLng(double lat, double lng) {
+    if (lng > 180.0) {
+      lng = -1.0 * (180.0 - (lng - 180.0));
+    } else if (lng < -180.0) {
+      lng = (lng + 180.0) + 180.0;
+    }
+    return new LatLng(lat, lng);
+  }
+
+  // =============================================== Getter / Setters ================================================
+
+  /**
+   * Returns the lower left LatLng
+   *
+   * @return Lower left LatLng
+   */
+  public LatLng getLowerLeft() {
+    return lowerLeft;
+  }
+
+  /**
+   * Returns the upper right corner LatLng
+   *
+   * @return Upper right corner LatLng
+   */
+  public LatLng getUpperRight() {
+    return upperRight;
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java	(revision 812248)
@@ -1,145 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-
-/**
- * 2D vector
- *
- * <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 Vector2D {
-  private double x;
-  private double y;
-
-  /**
-   * Create a vector from the origin of the coordinate system to the given
-   * point
-   * 
-   * @param x
-   * @param y
-   */
-  public Vector2D(double x, double y) {
-    this.x = x;
-    this.y = y;
-  }
-
-  /**
-   * Create a vector from the origin of the coordinate system to the given
-   * point
-   */
-  public Vector2D(Point2D p) {
-    this(p.getX(), p.getY());
-  }
-
-  /**
-   * Create a vector from one point to another
-   * 
-   * @param from
-   * @param to
-   */
-  public Vector2D(Point2D from, Point2D to) {
-    this(to.getX() - from.getX(), to.getY() - from.getY());
-  }
-
-  public Vector2D() {
-    this.x = 0;
-    this.y = 0;
-  }
-
-  public Vector2D(Vector2D other) {
-    this.x = other.x;
-    this.y = other.y;
-  }
-
-  public double getX() {
-    return x;
-  }
-
-  public double getY() {
-    return y;
-  }
-
-  public void setX(double x) {
-    this.x = x;
-  }
-
-  public void setY(double y) {
-    this.y = y;
-  }
-
-  public void set(double x, double y) {
-    this.x = x;
-    this.y = y;
-  }
-
-  public boolean equals(Vector2D other) {
-    return other != null && x == other.x && y == other.y;
-  }
-
-  public double dot(Vector2D in) {
-    return ((x) * in.x) + (y * in.y);
-  }
-
-  /**
-   * Vector length (magnitude) squared
-   */
-  public double normSqr() {
-    // Cast to F to prevent overflows
-    return (x * x) + (y * y);
-  }
-
-  public double norm() {
-    return Math.sqrt(normSqr());
-  }
-
-  public Vector2D mult(double d) {
-    return new Vector2D(x*d, y*d);
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = 1;
-    long temp;
-    temp = Double.doubleToLongBits(x);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(y);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (obj == null)
-      return false;
-    if (getClass() != obj.getClass())
-      return false;
-    Vector2D other = (Vector2D) obj;
-    if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
-      return false;
-    if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
-      return false;
-    return true;
-  }
-  
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java	(revision 834707)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java	(revision 834707)
@@ -1,125 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-/**
- * Imported from mq java client.  No changes made.
- *
- * <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 has been replaced with more accurate
- * math in {@link LLRect}. This class will be removed in a future release.
- */
-public class DistanceApproximation
-{
-  private double m_testLat;
-  private double m_testLng;
-  private double m_mpd;
-  private static final double m_milesPerLngDeg[]={
-     69.170976f, 69.160441f, 69.128838f, 69.076177f, 69.002475f,
-     68.907753f, 68.792041f, 68.655373f, 68.497792f, 68.319345f,
-     68.120088f, 67.900079f, 67.659387f, 67.398085f, 67.116253f,
-     66.813976f, 66.491346f, 66.148462f, 65.785428f, 65.402355f,
-     64.999359f, 64.576564f, 64.134098f, 63.672096f, 63.190698f,
-     62.690052f, 62.170310f, 61.631630f, 61.074176f, 60.498118f,
-     59.903632f, 59.290899f, 58.660106f, 58.011443f, 57.345111f,
-     56.661310f, 55.960250f, 55.242144f, 54.507211f, 53.755675f,
-     52.987764f, 52.203713f, 51.403761f, 50.588151f, 49.757131f,
-     48.910956f, 48.049882f, 47.174172f, 46.284093f, 45.379915f,
-     44.461915f, 43.530372f, 42.585570f, 41.627796f, 40.657342f,
-     39.674504f, 38.679582f, 37.672877f, 36.654698f, 35.625354f,
-     34.585159f, 33.534429f, 32.473485f, 31.402650f, 30.322249f,
-     29.232613f, 28.134073f, 27.026963f, 25.911621f, 24.788387f,
-     23.657602f, 22.519612f, 21.374762f, 20.223401f, 19.065881f,
-     17.902554f, 16.733774f, 15.559897f, 14.381280f, 13.198283f,
-     12.011266f, 10.820591f,  9.626619f,  8.429716f,  7.230245f,
-      6.028572f,  4.825062f,  3.620083f,  2.414002f,  1.207185f,
-      1.000000f};
-
-  public static final double MILES_PER_LATITUDE   = 69.170976f;
-  public static final double KILOMETERS_PER_MILE  = 1.609347f;
-
-
-  public DistanceApproximation()
-  {
-  }
-
-  public void setTestPoint(double lat, double lng)
-  {
-    m_testLat = lat;
-    m_testLng = lng;
-    m_mpd     = m_milesPerLngDeg[(int)(Math.abs(lat) + 0.5f)];
-  }
-
-  // Approximate arc distance between 2 lat,lng positions using miles per
-  //    latitude and longitude degree
-  public double getDistanceSq(double lat, double lng)
-  {
-    double latMiles = (lat - m_testLat) * MILES_PER_LATITUDE;
-
-    // Approximate longitude miles using the miles per degree assuming the
-    //    middle latitude/longitude.  This is less accurate at high (near
-    //    polar) latitudes but no road network is present at the poles!
-    //    If we ever have any roads crossing the international date we will
-    //    have to worry about that case.
-    double lngMiles = (lng - m_testLng) * m_mpd;
-
-     // Find the squared distance by the Pythagorean theorem (without sqrt)
-    return (latMiles * latMiles + lngMiles * lngMiles);
-  }
-
-  // Approximate arc distance between a segment (with lat,lng endpoints) and
-  //    the test position
-  public double getDistanceSq(double lat1, double lng1, double lat2, double lng2)
-  {
-     // Check if lat1,lng1 is closest point.  Construct a vector from point1
-     //    to point2 (v1) and another from point 1 to the test point (v2).
-     //    If dot product is negative then point 1 is the closest point
-     double v1y = lat2 - lat1;
-     double v1x = lng2 - lng1;
-     double v2y = m_testLat - lat1;
-     double v2x = m_testLng - lng1;
-     double dot = v1x * v2x + v1y * v2y;
-     if (dot <= 0.0f)
-        return getDistanceSq(lat1, lng1);
-
-     // Get the component of vector v2 along v1.  If component is greater
-     //    than 1 then the endpoint is the closest point.
-     double c = dot / (v1x * v1x + v1y * v1y);
-     if (c >= 1.0f)
-        return getDistanceSq(lat2, lng2);
-
-     // Since we are working io lat,lng space we need to find the point
-     //    along p1->p2 such that q->pt is perpendicular to p1->p2.  We
-     //    then find the distance squared between Q and pt.
-     return getDistanceSq((lat1 + v1y * c), (lng1 + v1x * c));
-  }
-
-  // Return the number of miles per degree of longitude
-  public static double getMilesPerLngDeg(double lat)
-  {
-     return (Math.abs(lat) <= 90.0) ? m_milesPerLngDeg[(int)(Math.abs(lat) + 0.5f)] : 69.170976f;
-  }
-  
-  public static double getMilesPerLatDeg() {
-    return MILES_PER_LATITUDE;
-  }
-}
-
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java	(revision 810951)
@@ -1,67 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.tier;
-
-import org.apache.lucene.spatial.geometry.DistanceUnits;
-import org.apache.lucene.spatial.geometry.FloatLatLng;
-import org.apache.lucene.spatial.geometry.LatLng;
-import org.apache.lucene.spatial.geometry.shape.LLRect;
-import org.apache.lucene.spatial.geometry.shape.Rectangle;
-
-/**
- * <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 DistanceUtils {
-
-  static DistanceUtils instance = new DistanceUtils();
-  
-  public static DistanceUtils getInstance()
-  {
-    return instance;
-  }
-
-  
-  public double getDistanceMi(double x1, double y1, double x2, double y2) {
-    return getLLMDistance(x1, y1, x2, y2);
-  }
-
-  /**
-   * 
-   * @param x1
-   * @param y1
-   * @param miles
-   * @return boundary rectangle where getY/getX is top left, getMinY/getMinX is bottom right
-   */
-  public Rectangle getBoundary (double x1, double y1, double miles) {
-
-    LLRect box = LLRect.createBox( new FloatLatLng( x1, y1 ), miles, miles );
-    
-    //System.out.println("Box: "+maxX+" | "+ maxY+" | "+ minX + " | "+ minY);
-    return box.toRectangle();
-
-  }
-  
-  public double getLLMDistance (double x1, double y1, double x2, double y2) {  
-
-    LatLng p1 = new FloatLatLng( x1, y1 );
-    LatLng p2 = new FloatLatLng( x2, y2 );
-    return p1.arcDistance( p2, DistanceUnits.MILES );
-  }
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/distance/NoOpDistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/distance/NoOpDistanceFilter.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/NoOpDistanceFilter.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,61 @@
+/**
+ * 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.distance;
+
+import org.apache.lucene.index.IndexReader;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Implementation of {@link DistanceFilter} that does no actual filtering.  This means
+ * that there can always be a DistanceFilter instantiated in {@link org.apache.lucene.spatial.SpatialFilter}, but that
+ * the actual process of filtering documents by their distance, which is a computationally expensive process, doesn't
+ * always have to occur.
+ *
+ * <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 NoOpDistanceFilter implements DistanceFilter {
+
+  private Map<Integer, Double> distancesById = Collections.EMPTY_MAP;
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<Integer, Double> getDistances() {
+    return distancesById;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Double getDistance(int docId) {
+    return distancesById.get(docId);
+  }
+
+  /**
+   * Executes no filtering.  Simply returns the given BitSet
+   * <p/>
+   * {@inheritDoc}
+   */
+  public BitSet bits(IndexReader reader, BitSet bits) throws IOException {
+    return bits;
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/distance/LocationDataSet.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/distance/LocationDataSet.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/LocationDataSet.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,38 @@
+/**
+ * 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.distance;
+
+import org.apache.lucene.spatial.geometry.Point;
+
+/**
+ * LocationDataSet is an abstracts away the format of the data that defines the location of a document.  It means that
+ * different formats can be used, whether they be 2 fields for latitude and longitude, or 1 field with a geohash, without
+ * having to change the code that uses the data.
+ *
+ * <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 interface LocationDataSet {
+
+  /**
+   * Returns the point (defined by an x/y coordinate) of the document with the given id
+   *
+   * @param docId ID of the document whose point is to be returned
+   * @return Point (defined by an x/y coordinate) of the document with the given id
+   */
+  Point getPoint(int docId);
+}
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/TestSpatialFilter.java	Wed Dec 09 16:23:54 CET 2009
@@ -14,507 +14,388 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.lucene.spatial.tier;
 
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+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.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.TopDocs;
+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.spatial.geohash.GeoHashUtils;
-import org.apache.lucene.spatial.geometry.DistanceUnits;
-import org.apache.lucene.spatial.geometry.FloatLatLng;
-import org.apache.lucene.spatial.geometry.LatLng;
-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.search.function.ValueSourceQuery;
+import org.apache.lucene.spatial.geometry.ArcDistanceCalculator;
+import org.apache.lucene.spatial.tier.CartesianTierPlotter;
+import org.apache.lucene.spatial.tier.projection.Projector;
+import org.apache.lucene.spatial.tier.projection.SinusoidalProjector;
+import org.apache.lucene.spatial.util.DistanceUnit;
+import org.apache.lucene.spatial.util.GeoHashUtils;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.NumericUtils;
 
-/**
- *
- */
-public class TestCartesian extends TestCase{
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
-  /**
+/**
-   * @param args
+ * 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;
   // reston va
-  private double lat = 38.969398; 
+  private double lat = 38.969398;
-  private double lng= -77.386398;
+  private double lng = -77.386398;
   private String latField = "lat";
   private String lngField = "lng";
-  private List<CartesianTierPlotter> ctps = new LinkedList<CartesianTierPlotter>();
-  private String geoHashPrefix = "_geoHash_";
+  private String geoHashField = "_geoHash_";
-  
+
-  private IProjector project = new SinusoidalProjector();
+  private List<CartesianTierPlotter> cartesianTierPlotters;
+  private Projector 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);
-    
-    setUpPlotter( 2, 15);
-    
     addData(writer);
-    
   }
-  
+
-  
-  private void setUpPlotter(int base, int top) {
-    
-    for (; base <= top; base ++){
-      ctps.add(new CartesianTierPlotter(base,project,
-          CartesianTierPlotter.DEFALT_FIELD_PREFIX));
-    }
-  }
-  
-  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));
-    
-    int ctpsize = ctps.size();
-    for (int i =0; i < ctpsize; i++){
-      CartesianTierPlotter ctp = ctps.get(i);
-      doc.add(new Field(ctp.getTierFieldName(), 
-          NumericUtils.doubleToPrefixCoded(ctp.getTierBoxId(lat,lng)),
-          Field.Store.YES, 
-          Field.Index.NOT_ANALYZED_NO_NORMS));
-      
-      doc.add(new Field(geoHashPrefix, GeoHashUtils.encode(lat,lng), 
-    		  Field.Store.YES, 
-    		  Field.Index.NOT_ANALYZED_NO_NORMS));
-    }
-    writer.addDocument(doc);
-    
-  }
-  
-  
-  
-  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();
-  }
-
-
-  public void testDistances() throws IOException, InvalidGeoException {
-    LatLng p1 = new FloatLatLng( 7.06, 171.2 );
-    LatLng p2 = new FloatLatLng( 21.6032207, -158.0 );
-    double miles = p1.arcDistance( p2, DistanceUnits.MILES );
-    System.out.println("testDistances");
-    System.out.println("miles:" + miles);
-    assertEquals(2288.82495932794, miles); 
-    LatLng p3 = new FloatLatLng( 41.6032207, -73.087749);
-    LatLng p4 = new FloatLatLng( 55.0, 4.0 );
-    miles = p3.arcDistance( p4, DistanceUnits.MILES );
-    System.out.println("miles:" + miles);
-    assertEquals(3474.331719997617, miles); 
-  }
-
-  public void testAntiM() throws IOException, InvalidGeoException {
+  public void testAntiM() throws IOException {
     searcher = new IndexSearcher(directory, true);
 
     final double miles = 2800.0;
-        // Hawaii
-        // 2300 miles to Marshall Island Airfield
+    // Hawaii - 2300 miles to Marshall Island Airfield
     lat = 21.6032207;
     lng = -158.0;
 
-    System.out.println("testAntiM");
-    // create a distance query
-    final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
-        latField, lngField, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true);
+    // Create a term query to search against all documents
+    Query termQuery = new TermQuery(new Term("metafile", "doc"));
 
-    System.out.println(dq);
-    //create a term query to search against all documents
-    Query tq = new TermQuery(new Term("metafile", "doc"));
+    // create a Spatial Filter
+    final SpatialFilter spatialFilter = new SpatialFilter(
+        lat,
+        lng,
+        miles,
+        DistanceUnit.MILES,
+        latField,
+        lngField,
+        TIER_FIELD_PREFIX,
+        new ArcDistanceCalculator(),
+        termQuery);
 
     FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+    CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fsQuery, spatialFilter, miles);
 
-    CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
-
-      @Override
-      public float customScore(int doc, float subQueryScore, float valSrcScore){
-        System.out.println(doc);
-        if (dq.distanceFilter.getDistance(doc) == null)
-          return 0;
-
-        double distance = dq.distanceFilter.getDistance(doc);
-        // boost score shouldn't exceed 1
-        if (distance < 1.0d)
-          distance = 1.0d;
-        //boost by distance is invertly proportional to
-        // to distance from center point to location
-        float score = (float) ((miles - distance) / miles );
-        return score * subQueryScore;
-      }
-    };
     // Create a distance sort
-    // As the radius filter has performed the distance calculations
-    // already, pass in the filter to reuse the results.
-    //
-    DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
+    // As the radius filter has performed the distance calculations already, pass in the filter to reuse the results.
+    DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(spatialFilter.getDistanceFilter());
-    Sort sort = new Sort(new SortField("foo", dsort,false));
+    Sort sort = new Sort(new SortField("foo", dsort, false));
 
-    // Perform the search, using the term query, the serial chain filter, and the
-    // distance sort
-    TopDocs hits = searcher.search(customScore.createWeight(searcher),null, 1000, sort);
+    // 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; 
     
     // Get a list of distances
-    Map<Integer,Double> distances = dq.distanceFilter.getDistances();
+    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.
-
+    // 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.
 
-
-    System.out.println("Distance Filter filtered: " + distances.size());
-    System.out.println("Results: " + results);
-    System.out.println("=============================");
-    System.out.println("Distances should be 2 "+ distances.size());
-    System.out.println("Results should be 2 "+ results);
-
     assertEquals(2, distances.size()); // fixed a store of only needed distances
     assertEquals(2, results);
+
     double lastDistance = 0;
-    for(int i =0 ; i < results; i++){
+    for(int i = 0 ; i < results; i++){
-      Document d = searcher.doc(scoreDocs[i].doc);
+      Double geoDistance = distances.get(scoreDocs[i].doc);
 
-      String name = d.get("name");
-      double rsLat = NumericUtils.prefixCodedToDouble(d.get(latField));
-      double rsLng = NumericUtils.prefixCodedToDouble(d.get(lngField));
-      Double geo_distance = distances.get(scoreDocs[i].doc);
-
-      double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng);
-      double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng);
-      System.out.println("Name: "+ name +", Distance "+ distance); //(res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
-      assertTrue(Math.abs((distance - llm)) < 1);
-      assertTrue((distance < miles ));
-      assertTrue(geo_distance >= lastDistance);
-      lastDistance = geo_distance;
+      assertTrue(geoDistance < miles);
+      assertTrue(geoDistance >= lastDistance);
+      lastDistance = geoDistance;
     }
   }
 
-  public void testPoleFlipping() throws IOException, InvalidGeoException {
+  public void testPoleFlipping() throws IOException {
     searcher = new IndexSearcher(directory, true);
 
     final double miles = 3500.0;
     lat = 41.6032207;
     lng = -73.087749;
 
-    System.out.println("testPoleFlipping");
-
-    // create a distance query
-    final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
-        latField, lngField, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true);
-
-    System.out.println(dq);
     //create a term query to search against all documents
-    Query tq = new TermQuery(new Term("metafile", "doc"));
+    Query termQuery = new TermQuery(new Term("metafile", "doc"));
+    // create a Spatial Filter
+    final SpatialFilter spatialFilter = new SpatialFilter(
+        lat,
+        lng,
+        miles,
+        DistanceUnit.MILES,
+        latField,
+        lngField,
+        TIER_FIELD_PREFIX,
+        new ArcDistanceCalculator(),
+        termQuery);
 
     FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+    CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fsQuery, spatialFilter, miles);
 
-    CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
-
-      @Override
-      public float customScore(int doc, float subQueryScore, float valSrcScore){
-        System.out.println(doc);
-        if (dq.distanceFilter.getDistance(doc) == null)
-          return 0;
-
-        double distance = dq.distanceFilter.getDistance(doc);
-        // boost score shouldn't exceed 1
-        if (distance < 1.0d)
-          distance = 1.0d;
-        //boost by distance is invertly proportional to
-        // to distance from center point to location
-        float score = (float) ((miles - distance) / miles );
-        return score * subQueryScore;
-      }
-    };
     // Create a distance sort
-    // As the radius filter has performed the distance calculations
-    // already, pass in the filter to reuse the results.
+    // As the radius filter has performed the distance calculations already, pass in the filter to reuse the results.
     //
-    DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
+    DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(spatialFilter.getDistanceFilter());
-    Sort sort = new Sort(new SortField("foo", dsort,false));
+    Sort sort = new Sort(new SortField("foo", dsort, false));
 
-    // Perform the search, using the term query, the serial chain filter, and the
-    // distance sort
-    TopDocs hits = searcher.search(customScore.createWeight(searcher),null, 1000, sort);
+    // 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; 
 
     // Get a list of distances
-    Map<Integer,Double> distances = dq.distanceFilter.getDistances();
+    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.
-
+    // 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.
 
-
-    System.out.println("Distance Filter filtered: " + distances.size());
-    System.out.println("Results: " + results);
-    System.out.println("=============================");
-    System.out.println("Distances should be 18 "+ distances.size());
-    System.out.println("Results should be 18 "+ results);
-
     assertEquals(18, distances.size()); // fixed a store of only needed distances
     assertEquals(18, results);
+    
     double lastDistance = 0;
     for(int i =0 ; i < results; i++){
-      Document d = searcher.doc(scoreDocs[i].doc);
-      String name = d.get("name");
-      double rsLat = NumericUtils.prefixCodedToDouble(d.get(latField));
-      double rsLng = NumericUtils.prefixCodedToDouble(d.get(lngField));
-      Double geo_distance = distances.get(scoreDocs[i].doc);
+      Double geoDistance = distances.get(scoreDocs[i].doc);
 
-      double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng);
-      double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng);
-      System.out.println("Name: "+ name +", Distance "+ distance); //(res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
-      assertTrue(Math.abs((distance - llm)) < 1);
-      System.out.println("checking limit "+ distance + " < " + miles);
-      assertTrue((distance < miles ));
-      System.out.println("checking sort "+ geo_distance + " >= " + lastDistance);
-      assertTrue(geo_distance >= lastDistance);
-      lastDistance = geo_distance;
+      assertTrue(geoDistance < miles);
+      assertTrue(geoDistance >= lastDistance);
+      lastDistance = geoDistance;
     }
   }
   
-  public void testRange() throws IOException, InvalidGeoException {
+  public void testRange() throws IOException {
     searcher = new IndexSearcher(directory, true);
 
     final double[] milesToTest = new double[] {6.0, 0.5, 0.001, 0.0};
     final int[] expected = new int[] {7, 1, 0, 0};
 
-    for(int x=0;x<expected.length;x++) {
+    for(int x = 0; x < expected.length; x++) {
-    
       final double miles = milesToTest[x];
-    
+
-      // create a distance query
-      final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles, 
-                                                               latField, lngField, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true);
-     
-      System.out.println(dq);
       //create a term query to search against all documents
-      Query tq = new TermQuery(new Term("metafile", "doc"));
+      Query termQuery = new TermQuery(new Term("metafile", "doc"));
-    
+
+      final SpatialFilter spatialFilter = new SpatialFilter(
+          lat,
+          lng,
+          miles,
+          DistanceUnit.MILES,
+          latField,
+          lngField,
+          TIER_FIELD_PREFIX,
+          new ArcDistanceCalculator(),
+          termQuery);
+    
       FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
+      CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fsQuery, spatialFilter, miles);
-    
+      
-      CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
-      
-          @Override
-            public float customScore(int doc, float subQueryScore, float valSrcScore){
-            //System.out.println(doc);
-            if (dq.distanceFilter.getDistance(doc) == null)
-              return 0;
-        
-            double distance = dq.distanceFilter.getDistance(doc);
-            // boost score shouldn't exceed 1
-            if (distance < 1.0d)
-              distance = 1.0d;
-            //boost by distance is invertly proportional to
-            // to distance from center point to location
-            float score = (float) ( (miles - distance) / miles );
-            return score * subQueryScore;
-          }
-        };
       // Create a distance sort
-      // As the radius filter has performed the distance calculations
-      // already, pass in the filter to reuse the results.
-      // 
-      DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
+      // As the radius filter has performed the distance calculations already, pass in the filter to reuse the results.
+      DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(spatialFilter.getDistanceFilter());
-      Sort sort = new Sort(new SortField("foo", dsort,false));
+      Sort sort = new Sort(new SortField("foo", dsort, false));
     
-      // Perform the search, using the term query, the serial chain filter, and the
-      // distance sort
-      TopDocs hits = searcher.search(customScore.createWeight(searcher),null, 1000, sort);
+      // 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; 
     
       // Get a list of distances 
-      Map<Integer,Double> distances = dq.distanceFilter.getDistances();
+      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.
-    
+      // 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.
-    
+
-    
-      System.out.println("Distance Filter filtered: " + distances.size());
-      System.out.println("Results: " + results);
-      System.out.println("=============================");
-      System.out.println("Distances should be 7 "+ expected[x] + ":" + distances.size());
-      System.out.println("Results should be 7 "+ expected[x] + ":" + results);
-
       assertEquals(expected[x], distances.size()); // fixed a store of only needed distances
       assertEquals(expected[x], results);
+      
       double lastDistance = 0;
       for(int i =0 ; i < results; i++){
-        Document d = searcher.doc(scoreDocs[i].doc);
+        Double geoDistance = distances.get(scoreDocs[i].doc);
-      
+
-        String name = d.get("name");
-        double rsLat = NumericUtils.prefixCodedToDouble(d.get(latField));
-        double rsLng = NumericUtils.prefixCodedToDouble(d.get(lngField)); 
-        Double geo_distance = distances.get(scoreDocs[i].doc);
-      
-        double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng);
-        double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng);
-        System.out.println("Name: "+ name +", Distance "+ distance); //(res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
-        assertTrue(Math.abs((distance - llm)) < 1);
-        assertTrue((distance < miles ));
-        assertTrue(geo_distance > lastDistance);
-        lastDistance = geo_distance;
+        assertTrue((geoDistance < miles));
+        assertTrue(geoDistance > lastDistance);
+        lastDistance = geoDistance;
       }
     }
   }
-  
+
-  
-  
-  public void testGeoHashRange() throws IOException, InvalidGeoException {
+  public void testGeoHashRange() throws IOException {
     searcher = new IndexSearcher(directory, true);
-	    
+	  
     final double[] milesToTest = new double[] {6.0, 0.5, 0.001, 0.0};
     final int[] expected = new int[] {7, 1, 0, 0};
 
-    for(int x=0;x<expected.length;x++) {
+    for(int x = 0; x < expected.length; x++) {
       final double miles = milesToTest[x];
-	    
+
-      // create a distance query
-      final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles, 
-                                                               geoHashPrefix, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true);
-	     
-      System.out.println(dq);
       //create a term query to search against all documents
-      Query tq = new TermQuery(new Term("metafile", "doc"));
+      Query termQuery = new TermQuery(new Term("metafile", "doc"));
+      final SpatialFilter spatialFilter = new SpatialFilter(
+          lat,
+          lng,
+          miles,
+          DistanceUnit.MILES,
+          geoHashField,
+          TIER_FIELD_PREFIX,
+          new ArcDistanceCalculator(),
+          termQuery);
 	    
       FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
-      CustomScoreQuery customScore = new CustomScoreQuery(tq,fsQuery){
+      CustomScoreQuery customScoreQuery = new DistanceCustomScoreQuery(termQuery, fsQuery, spatialFilter, miles);
-	      
+	    
-          @Override
-            public float customScore(int doc, float subQueryScore, float valSrcScore){
-            //System.out.println(doc);
-            if (dq.distanceFilter.getDistance(doc) == null)
-              return 0;
-	        
-            double distance = dq.distanceFilter.getDistance(doc);
-            // boost score shouldn't exceed 1
-            if (distance < 1.0d)
-              distance = 1.0d;
-            //boost by distance is invertly proportional to
-            // to distance from center point to location
-            float score = (float) ( (miles - distance) / miles );
-            return score * subQueryScore;
-          }
-        };
-      // Create a distance sort
-      // As the radius filter has performed the distance calculations
-      // already, pass in the filter to reuse the results.
-      // 
-      DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
-      Sort sort = new Sort(new SortField("foo", dsort));
-	    
-      // Perform the search, using the term query, the serial chain filter, and the
-      // distance sort
-      TopDocs hits = searcher.search(customScore.createWeight(searcher),dq.getFilter(), 1000); //,sort);
+      // 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; 
 	    
       // Get a list of distances 
-      Map<Integer,Double> distances = dq.distanceFilter.getDistances();
+      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.
-	    
+      // 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.
-	    
+
-	    
-      System.out.println("Distance Filter filtered: " + distances.size());
-      System.out.println("Results: " + results);
-      System.out.println("=============================");
-      System.out.println("Distances should be 14 "+ expected[x] + ":" + distances.size());
-      System.out.println("Results should be 7 "+ expected[x] + ":" + results);
-
       assertEquals(expected[x], distances.size());
       assertEquals(expected[x], results);
 	    
-      for(int i =0 ; i < results; i++){
+      for(int i = 0; i < results; i++){
-        Document d = searcher.doc(scoreDocs[i].doc);
+        Double geoDistance = distances.get(scoreDocs[i].doc);
+        assertTrue(geoDistance < miles );
+      }
+    }
+  }
-	      
+
-        String name = d.get("name");
-        double rsLat = NumericUtils.prefixCodedToDouble(d.get(latField));
-        double rsLng = NumericUtils.prefixCodedToDouble(d.get(lngField)); 
-        Double geo_distance = distances.get(scoreDocs[i].doc);
+  // ================================================= Helper Methods ================================================
-	      
+
-        double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng);
-        double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng);
-        System.out.println("Name: "+ name +", Distance (res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ scoreDocs[i].score);
-        assertTrue(Math.abs((distance - llm)) < 1);
-        assertTrue((distance < miles ));
+  /**
+   * 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/distance/LocationDataSetFactory.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/distance/LocationDataSetFactory.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/LocationDataSetFactory.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,39 @@
+/**
+ * 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.distance;
+
+import org.apache.lucene.index.IndexReader;
+
+import java.io.IOException;
+
+/**
+ * Factory for instances of {@link LocationDataSet}
+ *
+ * <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 interface LocationDataSetFactory {
+
+  /**
+   * Builds a LocationDataSet based on the data read from the given IndexReader
+   *
+   * @param indexReader IndexReader from where the location data will be read
+   * @return LocationDataSet representing the location data of the documents in the index
+   * @throws java.io.IOException Can be thrown while reading from the IndexReader
+   */
+  LocationDataSet buildLocationDataSet(IndexReader indexReader) throws IOException;
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java	Wed Dec 09 16:23:54 CET 2009
@@ -15,160 +15,126 @@
  * limitations under the License.
  */
 
-package org.apache.lucene.spatial.geohash;
+package org.apache.lucene.spatial.util;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Based on http://en.wikipedia.org/wiki/Geohash
+ * Utilities for encoding and decoding geohashes. Based on http://en.wikipedia.org/wiki/Geohash.
- *
+ * 
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * <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 GeoHashUtils {
 
-	// geohash's char map
-	// no a's i's l's o's
-	// old MacDonal wouldn't be happy
-	private static char[] _base32 = {'0','1','2','3','4','5','6','7','8','9',
-							'b','c','d','e','f','g','h','j','k','m',
-							'n','p','q','r','s','t','u','v','w','x',
-							'y','z'} ;
+  private static char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+                                   'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
-	
+
-	private final static Map<Character, Integer> _decodemap = new HashMap<Character, Integer>();
+  private final static Map<Character, Integer> DECODE_MAP = new HashMap<Character, Integer>();
+
+  private static int PRECISION = 12;
+  private static int[] BITS = {16, 8, 4, 2, 1};
+
-	static {
+  static {
-		int sz = _base32.length;
-		for (int i = 0; i < sz; i++ ){
-			_decodemap.put(_base32[i], i);
+    for (int i = 0; i < BASE_32.length; i++) {
+      DECODE_MAP.put(BASE_32[i], i);
-		}
-	}
-	
+    }
+  }
+
-	private static int precision = 12;
-	private static int[] bits = {16, 8, 4, 2, 1};
-	
-	public static void main(String[] args) {
-		GeoHashUtils ghf = new GeoHashUtils();
-		String gc1 = ghf.encode(30, -90.0);
-		String gc2 = ghf.encode(51.4797, -0.0124);
-		
-		System.out.println(gc1);
-		System.out.println(gc2);
-		
-		double [] gd1 = ghf.decode(gc1);
-		double [] gd2 = ghf.decode(gc2);
-		System.out.println(gd1[0]+ ", "+ gd1[1]);
-		System.out.println(gd2[0]+ ", "+ gd2[1]);
-		
+  private GeoHashUtils() {
-	}
-	
+  }
+
+  /**
+   * Encodes the given latitude and longitude into a geohash
+   *
+   * @param latitude Latitude to encode
+   * @param longitude Longitude to encode
+   * @return Geohash encoding of the longitude and latitude
+   */
-	public static String encode(double latitude, double longitude){
+  public static String encode(double latitude, double longitude) {
-		double[] lat_interval = {-90.0 ,  90.0};
-		double[] lon_interval = {-180.0, 180.0};
+    double[] latInterval = {-90.0, 90.0};
+    double[] lngInterval = {-180.0, 180.0};
-			
-		StringBuilder geohash = new StringBuilder();
+
+    StringBuilder geohash = new StringBuilder();
-		boolean is_even = true;
-		int bit = 0, ch = 0;
+    boolean isEven = true;
-		
+
-		while(geohash.length() < precision){
+    int bit = 0;
+    int ch = 0;
+
+    while (geohash.length() < PRECISION) {
-			double mid = 0.0;
+      double mid = 0.0;
-			if(is_even){
-				mid = (lon_interval[0] + lon_interval[1]) / 2;
+      if (isEven) {
+        mid = (lngInterval[0] + lngInterval[1]) / 2D;
-				if (longitude > mid){
+        if (longitude > mid) {
-					ch |= bits[bit];
-					lon_interval[0] = mid;
+          ch |= BITS[bit];
+          lngInterval[0] = mid;
-				} else {
+        } else {
-					lon_interval[1] = mid;
+          lngInterval[1] = mid;
-				}
-				
-			} else {
+        }
+
+      } else {
-				mid = (lat_interval[0] + lat_interval[1]) / 2;
+        mid = (latInterval[0] + latInterval[1]) / 2D;
-				if(latitude > mid){
+        if (latitude > mid) {
-					ch |= bits[bit];
-					lat_interval[0] = mid;
+          ch |= BITS[bit];
+          latInterval[0] = mid;
-				} else {
+        } else {
-					lat_interval[1] = mid;
+          latInterval[1] = mid;
-				}
-			}
-			
+        }
+      }
+
-			is_even = is_even ? false : true;
+      isEven = !isEven;
-			
+
-			if (bit  < 4){
+      if (bit < 4) {
-				bit ++;
+        bit++;
-			} else {
+      } else {
-				geohash.append(_base32[ch]);
+        geohash.append(BASE_32[ch]);
-				bit =0;
+        bit = 0;
-				ch = 0;
-			}
-		}
-		
-		return geohash.toString();
-	}
-	
+        ch = 0;
+      }
+    }
+
+    return geohash.toString();
+  }
+
+  /**
+   * Decodes the given geohash into a latitude and longitude
+   *
+   * @param geohash Geohash to deocde
+   * @return Array with the latitude at index 0, and longitude at index 1
+   */
-	public static double[] decode(String geohash) {
+  public static double[] decode(String geohash) {
-		double[] ge = decode_exactly(geohash);
-		double lat, lon, lat_err, lon_err;
-		lat = ge[0];
-		lon = ge[1];
-		lat_err = ge[2];
-		lon_err = ge[3];
+    double[] latInterval = {-90.0, 90.0};
+    double[] lngInterval = {-180.0, 180.0};
-		
+
-		double lat_precision = Math.max(1, Math.round(- Math.log10(lat_err))) - 1;
-		double lon_precision = Math.max(1, Math.round(- Math.log10(lon_err))) - 1;
+    boolean isEven = true;
-		
+
-		lat = getPrecision(lat, lat_precision);
-		lon = getPrecision(lon, lon_precision);
+    double latitude;
+    double longitude;
+    for (int i = 0; i < geohash.length(); i++) {
+      int cd = DECODE_MAP.get(geohash.charAt(i));
-		
+
-		return new double[] {lat, lon};
-	}
-	
-	public static double[] decode_exactly (String geohash){
-		double[] lat_interval = {-90.0 , 90.0};
-		double[] lon_interval = {-180.0, 180.0};
-		
-		double lat_err =  90.0;
-		double lon_err = 180.0;
-		boolean is_even = true;
-		int sz = geohash.length();
-		int bsz = bits.length;
-		double latitude, longitude;
-		for (int i = 0; i < sz; i++){
-			
-			int cd = _decodemap.get(geohash.charAt(i));
-			
-			for (int z = 0; z< bsz; z++){
-				int mask = bits[z];
-				if (is_even){
-					lon_err /= 2;
+      for (int mask : BITS) {
+        if (isEven) {
-					if ((cd & mask) != 0){
+          if ((cd & mask) != 0) {
-						lon_interval[0] = (lon_interval[0]+lon_interval[1])/2;
+            lngInterval[0] = (lngInterval[0] + lngInterval[1]) / 2D;
-					} else {
+          } else {
-						lon_interval[1] = (lon_interval[0]+lon_interval[1])/2;
+            lngInterval[1] = (lngInterval[0] + lngInterval[1]) / 2D;
-					}
-					
-				} else {
+          }
+
+        } else {
-					lat_err /=2;
-				
-					if ( (cd & mask) != 0){
+          if ((cd & mask) != 0) {
-						lat_interval[0] = (lat_interval[0]+lat_interval[1])/2;
+            latInterval[0] = (latInterval[0] + latInterval[1]) / 2D;
-					} else {
+          } else {
-						lat_interval[1] = (lat_interval[0]+lat_interval[1])/2;
+            latInterval[1] = (latInterval[0] + latInterval[1]) / 2D;
-					}
-				}
+          }
+        }
-				is_even = is_even ? false : true;
+        isEven = !isEven;
-			}
-		
-		}
+      }
+
+    }
-		latitude  = (lat_interval[0] + lat_interval[1]) / 2;
-		longitude = (lon_interval[0] + lon_interval[1]) / 2;
+    latitude = (latInterval[0] + latInterval[1]) / 2;
+    longitude = (lngInterval[0] + lngInterval[1]) / 2;
 
-		return new double []{latitude, longitude, lat_err, lon_err};
+    return new double[] {latitude, longitude};
 	}
-	
-	static double getPrecision(double x, double precision) {
-		double base = Math.pow(10,- precision);
-		double diff = x % base;
-		return x - diff;
-	}
+}
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java	(revision 811070)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java	(revision 811070)
@@ -1,234 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-
-/**
- * Ellipse shape. From C++ gl.
- *
- * <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 Ellipse implements Geometry2D {
-  private Point2D center;
-
-  /**
-   * Half length of major axis
-   */
-  private double a;
-
-  /**
-   * Half length of minor axis
-   */
-  private double b;
-
-  private double k1, k2, k3;
-
-  /**
-   * sin of rotation angle
-   */
-  private double s;
-
-  /**
-   * cos of rotation angle
-   */
-  private double c;
-
-  public Ellipse() {
-    center = new Point2D(0, 0);
-  }
-
-  private double SQR(double d) {
-    return d * d;
-  }
-
-  /**
-   * Constructor given bounding rectangle and a rotation.
-   */
-  public Ellipse(Point2D p1, Point2D p2, double angle) {
-    center = new Point2D();
-
-    // Set the center
-    center.x((p1.x() + p2.x()) * 0.5f);
-    center.y((p1.y() + p2.y()) * 0.5f);
-
-    // Find sin and cos of the angle
-    double angleRad = Math.toRadians(angle);
-    c = Math.cos(angleRad);
-    s = Math.sin(angleRad);
-
-    // Find the half lengths of the semi-major and semi-minor axes
-    double dx = Math.abs(p2.x() - p1.x()) * 0.5;
-    double dy = Math.abs(p2.y() - p1.y()) * 0.5;
-    if (dx >= dy) {
-      a = dx;
-      b = dy;
-    } else {
-      a = dy;
-      b = dx;
-    }
-
-    // Find k1, k2, k3 - define when a point x,y is on the ellipse
-    k1 = SQR(c / a) + SQR(s / b);
-    k2 = 2 * s * c * ((1 / SQR(a)) - (1 / SQR(b)));
-    k3 = SQR(s / a) + SQR(c / b);
-  }
-
-  /**
-   * Determines if a line segment intersects the ellipse and if so finds the
-   * point(s) of intersection.
-   * 
-   * @param seg
-   *            Line segment to test for intersection
-   * @param pt0
-   *            OUT - intersection point (if it exists)
-   * @param pt1
-   *            OUT - second intersection point (if it exists)
-   * 
-   * @return Returns the number of intersection points (0, 1, or 2).
-   */
-  public int intersect(LineSegment seg, Point2D pt0, Point2D pt1) {
-    if (pt0 == null)
-      pt0 = new Point2D();
-    if (pt1 == null)
-      pt1 = new Point2D();
-
-    // Solution is found by parameterizing the line segment and
-    // substituting those values into the ellipse equation.
-    // Results in a quadratic equation.
-    double x1 = center.x();
-    double y1 = center.y();
-    double u1 = seg.A.x();
-    double v1 = seg.A.y();
-    double u2 = seg.B.x();
-    double v2 = seg.B.y();
-    double dx = u2 - u1;
-    double dy = v2 - v1;
-    double q0 = k1 * SQR(u1 - x1) + k2 * (u1 - x1) * (v1 - y1) + k3
-        * SQR(v1 - y1) - 1;
-    double q1 = (2 * k1 * dx * (u1 - x1)) + (k2 * dx * (v1 - y1))
-        + (k2 * dy * (u1 - x1)) + (2 * k3 * dy * (v1 - y1));
-    double q2 = (k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy));
-
-    // Compare q1^2 to 4*q0*q2 to see how quadratic solves
-    double d = SQR(q1) - (4 * q0 * q2);
-    if (d < 0) {
-      // Roots are complex valued. Line containing the segment does
-      // not intersect the ellipse
-      return 0;
-    }
-
-    if (d == 0) {
-      // One real-valued root - line is tangent to the ellipse
-      double t = -q1 / (2 * q2);
-      if (0 <= t && t <= 1) {
-        // Intersection occurs along line segment
-        pt0.x(u1 + t * dx);
-        pt0.y(v1 + t * dy);
-        return 1;
-      } else
-        return 0;
-    } else {
-      // Two distinct real-valued roots. Solve for the roots and see if
-      // they fall along the line segment
-      int n = 0;
-      double q = Math.sqrt(d);
-      double t = (-q1 - q) / (2 * q2);
-      if (0 <= t && t <= 1) {
-        // Intersection occurs along line segment
-        pt0.x(u1 + t * dx);
-        pt0.y(v1 + t * dy);
-        n++;
-      }
-
-      // 2nd root
-      t = (-q1 + q) / (2 * q2);
-      if (0 <= t && t <= 1) {
-        if (n == 0) {
-          pt0.x(u1 + t * dx);
-          pt0.y(v1 + t * dy);
-          n++;
-        } else {
-          pt1.x(u1 + t * dx);
-          pt1.y(v1 + t * dy);
-          n++;
-        }
-      }
-      return n;
-    }
-  }
-
-  public IntersectCase intersect(Rectangle r) {
-    // Test if all 4 corners of the rectangle are inside the ellipse
-    Point2D ul = new Point2D(r.MinPt().x(), r.MaxPt().y());
-    Point2D ur = new Point2D(r.MaxPt().x(), r.MaxPt().y());
-    Point2D ll = new Point2D(r.MinPt().x(), r.MinPt().y());
-    Point2D lr = new Point2D(r.MaxPt().x(), r.MinPt().y());
-    if (contains(ul) && contains(ur) && contains(ll) && contains(lr))
-      return IntersectCase.CONTAINS;
-
-    // Test if any of the rectangle edges intersect
-    Point2D pt0 = new Point2D(), pt1 = new Point2D();
-    LineSegment bottom = new LineSegment(ll, lr);
-    if (intersect(bottom, pt0, pt1) > 0)
-      return IntersectCase.INTERSECTS;
-
-    LineSegment top = new LineSegment(ul, ur);
-    if (intersect(top, pt0, pt1) > 0)
-      return IntersectCase.INTERSECTS;
-
-    LineSegment left = new LineSegment(ll, ul);
-    if (intersect(left, pt0, pt1) > 0)
-      return IntersectCase.INTERSECTS;
-
-    LineSegment right = new LineSegment(lr, ur);
-    if (intersect(right, pt0, pt1) > 0)
-      return IntersectCase.INTERSECTS;
-
-    // Ellipse does not intersect any edge : since the case for the ellipse
-    // containing the rectangle was considered above then if the center
-    // is inside the ellipse is fully inside and if center is outside
-    // the ellipse is fully outside
-    return (r.contains(center)) ? IntersectCase.WITHIN
-        : IntersectCase.OUTSIDE;
-  }
-
-  public double area() {
-    throw new UnsupportedOperationException();
-  }
-
-  public Point2D centroid() {
-    throw new UnsupportedOperationException();
-  }
-
-  public boolean contains(Point2D pt) {
-    // Plug in equation for ellipse, If evaluates to <= 0 then the
-    // point is in or on the ellipse.
-    double dx = pt.x() - center.x();
-    double dy = pt.y() - center.y();
-    double eq=(((k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy)) - 1));
-    
-    return eq<=0;
-  }
-
-  public void translate(Vector2D v) {
-    throw new UnsupportedOperationException();
-  }
-
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java	(revision 833867)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java	Wed Dec 09 16:23:54 CET 2009
@@ -17,144 +17,114 @@
 
 package org.apache.lucene.spatial.geometry;
 
+import org.apache.lucene.spatial.util.DistanceUnit;
 
 /**
- * Abstract base lat-lng class which can manipulate fixed point or floating
- * point based coordinates. Instances are immutable.
+ * Class representing a latitude longitude pair.
- * 
+ *
- * @see FloatLatLng
- *
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * <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 abstract class LatLng {
+public class LatLng {
 
-  public abstract boolean isNormalized();
+  private final static int LONGITUDE_DECIMAL_DEGREE_RANGE = 360;
+  private final static int LATITUDE_DECIMAL_DEGREE_RANGE = 180;
 
-  public abstract boolean isFixedPoint();
+  private final double lat;
+  private final double lng;
+  private boolean normalized;
 
-  public abstract LatLng normalize();
-
-  public abstract int getFixedLat();
-
-  public abstract int getFixedLng();
-
-  public abstract double getLat();
-
-  public abstract double getLng();
-
-  public abstract LatLng copy();
-
-  public abstract FixedLatLng toFixed();
-
-  public abstract FloatLatLng toFloat();
-  
   /**
-   * Convert the lat/lng into the cartesian coordinate plane such that all
-   * world coordinates are represented in the first quadrant.
-   * The x dimension corresponds to latitude and y corresponds to longitude.
-   * The translation starts with the normalized latlng and adds 180 to the latitude and 
-   * 90 to the longitude (subject to fixed point scaling).
+   * Creates a new LatLng with the given latitude and longitude
+   *
+   * @param lat Latitude part of the LatLng
+   * @param lng Longitude part of the LatLng
    */
-  public CartesianPoint toCartesian() {
-    LatLng ll=normalize();
-    
-    int lat=ll.getFixedLat();
-    int lng=ll.getFixedLng();
-    
-    return new CartesianPoint(
-        lng+180*FixedLatLng.SCALE_FACTOR_INT,
-        lat+90*FixedLatLng.SCALE_FACTOR_INT
-    );
+  public LatLng(double lat, double lng) {
+    if (lat > 90.0 || lat < -90.0) {
+      throw new IllegalArgumentException("Illegal latitude value " + lat);
-  }
+    }
-  
-  /**
-   * The inverse of toCartesian().  Always returns a FixedLatLng.
-   * @param pt
-   */
-  public static LatLng fromCartesian(CartesianPoint pt) {
-    int lat=pt.getY() - 90 * FixedLatLng.SCALE_FACTOR_INT;
-    int lng=pt.getX() - 180 * FixedLatLng.SCALE_FACTOR_INT;
-    
-    return new FixedLatLng(lat, lng);
+    this.lat = lat;
+    this.lng = lng;
   }
-  
+
   /**
    * Calculates the distance between two lat/lng's in miles.
-   * Imported from mq java client.
-   * 
+   *
-   * @param ll2
-   *            Second lat,lng position to calculate distance to.
-   * 
+   * @param ll2 Second lat,lng position to calculate distance to.
    * @return Returns the distance in miles.
    */
   public double arcDistance(LatLng ll2) {
-    return arcDistance(ll2, DistanceUnits.MILES);
+    return arcDistance(ll2, DistanceUnit.MILES);
   }
 
   /**
-   * Calculates the distance between two lat/lng's in miles or meters.
-   * Imported from mq java client.  Variable references changed to match.
+   * Calculates the distance between two lat/lng's in miles or kilometres
-   * 
+   *
-   * @param ll2
-   *            Second lat,lng position to calculate distance to.
-   * @param lUnits
-   *            Units to calculate distance, defaults to miles
-   * 
+   * @param targetLatLng Second lat,lng position to calculate distance to.
+   * @param distanceUnit Units to calculate distace, defaults to miles
    * @return Returns the distance in meters or miles.
    */
-  public double arcDistance(LatLng ll2, DistanceUnits lUnits) {
-    LatLng ll1 = normalize();
-    ll2 = ll2.normalize();
+  public double arcDistance(LatLng targetLatLng, DistanceUnit distanceUnit) {
+    LatLng srcLatLng = normalize();
+    targetLatLng = targetLatLng.normalize();
 
-    double lat1 = ll1.getLat(), lng1 = ll1.getLng();
-    double lat2 = ll2.getLat(), lng2 = ll2.getLng();
+    double srcLat = Math.toRadians(srcLatLng.getLat());
+    double srcLng = Math.toRadians(srcLatLng.getLng());
+    double targetLat = Math.toRadians(targetLatLng.getLat());
+    double targetLng = Math.toRadians(targetLatLng.getLng());
 
-    // Check for same position
-    if (lat1 == lat2 && lng1 == lng2)
-      return 0.0;
+    // TODO - Change to SpatialConstants
+    double radius = (distanceUnit == DistanceUnit.MILES) ? 3963.205/* MILERADIUSOFEARTH */
+        : 6378.160187/* KMRADIUSOFEARTH */;
 
-    // Get the m_dLongitude difference. Don't need to worry about
-    // crossing 180 since cos(x) = cos(-x)
-    double dLon = lng2 - lng1;
+    return Math.acos(Math.sin(srcLat) * Math.sin(targetLat) + Math.cos(srcLat) * Math.cos(targetLat) * Math.cos(targetLng - srcLng)) * radius;
+  }
 
-    double a = radians(90.0 - lat1);
-    double c = radians(90.0 - lat2);
-    double cosB = (Math.cos(a) * Math.cos(c))
-        + (Math.sin(a) * Math.sin(c) * Math.cos(radians(dLon)));
+  // =============================================== Helper methods ==================================================
 
-    double radius = (lUnits == DistanceUnits.MILES) ? 3963.205/* MILERADIUSOFEARTH */
-    : 6378.160187/* KMRADIUSOFEARTH */;
-
-    // Find angle subtended (with some bounds checking) in radians and
-    // multiply by earth radius to find the arc distance
-    if (cosB < -1.0)
-      return 3.14159265358979323846/* PI */* radius;
-    else if (cosB >= 1.0)
-      return 0;
-    else
-      return Math.acos(cosB) * radius;
+  /**
+   * Checks if {@code this} has already been normalized
+   *
+   * @return {@code true} if {@code this} is normalized, {@code false} otherwise
+   */
+  private boolean isNormalized() {
+    return normalized || ((lng >= -180) && (lng <= 180));
   }
 
-  private double radians(double a) {
-    return a * 0.01745329251994;
+  /**
+   * Normalizes the latitude/longitude of {@code this}, returning a new instance if any values have to change
+   *
+   * @return {@code this} if no normalization has to occur, otherwise a new instance with the normalized latitude and longitude
+   */
+  private LatLng normalize() {
+    if (isNormalized()) {
+      return this;
-  }
+    }
 
-  @Override
-  public String toString() {
-    return "[" + getLat() + "," + getLng() + "]";
+    double newLng = ((lng + LONGITUDE_DECIMAL_DEGREE_RANGE) % LONGITUDE_DECIMAL_DEGREE_RANGE) - LONGITUDE_DECIMAL_DEGREE_RANGE / 2.0;
+
+    LatLng ret = new LatLng(lat, newLng);
+    ret.normalized = true;
+    return ret;
   }
 
+  // =============================================== Getter / Setters ================================================
+
   /**
-   * Calculate the midpoint between this point an another.  Respects fixed vs floating point
-   * @param other
+   * Returns the latitude part of the LatLng
+   *
+   * @return Latitude part of the LatLng
    */
-  public abstract LatLng calculateMidpoint(LatLng other);
+  public double getLat() {
+    return this.lat;
+  }
-  
+
-  @Override
-  public abstract int hashCode();
-
-  @Override
-  public abstract boolean equals(Object obj);
+  /**
+   * Returns the longitude part of the LatLng
+   *
+   * @return Longitude part of the LatLng
+   */
+  public double getLng() {
+    return this.lng;
-}
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java	(revision 833122)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShape.java	Wed Dec 09 16:23:54 CET 2009
@@ -22,32 +22,53 @@
 import java.util.List;
 
 /**
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * CartesianShape contains information related to a shape created on a specific cartesian tier.  Because the tiers consist
+ * of a set of boxes, shapes are defined simply by what boxes the shape overlaps, and what tier level the boxes are at.
+ * Such a simply approximation will result in points being included that are not in the actual shape, but this fine grained
+ * filtering is left out of the cartesian shape filtering process.
+ *
+ * <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 Shape implements Serializable{
+public class CartesianShape implements Serializable {
 
-  private List<Double> area = new ArrayList<Double>();
-  private String tierId;
+  private List<Double> boxIds = new ArrayList<Double>();
+  private int tierId;
-  
+
-  public Shape (String tierId){
+  /**
+   * Creates a new CartesianShape that represents a shape at the given tier level
+   *
+   * @param tierId ID of the tier level that the shape is in
+   */
+  public CartesianShape(int tierId) {
     this.tierId = tierId;
   }
 
-  public void addBox(double  boxId){
-    area.add(boxId);
+  /**
+   * Adds the given box id to the list of boxes this shape overlaps
+   *
+   * @param boxId ID of a box that this shape overlaps
+   */
+  public void addBoxId(double boxId) {
+    boxIds.add(boxId);
   }
-  
+
-  public List<Double> getArea(){
-    return area;
+  // =============================================== Getters / Setters ===============================================
+
+  /**
+   * Returns the list of ids of the boxes that this shape overlaps
+   *
+   * @return List of ids of the boxes the shape overlaps.  Always non-null.
+   */
+  public List<Double> getBoxIds() {
+    return boxIds;
   }
-  
+
-  public String getTierId(){
+  /**
+   * Returns the tier level ID that this shape is in
+   *
+   * @return Tier level id that this shape is in
+   */
+  public int getTierId() {
     return tierId;
   }
-  
-  public boolean isInside(double boxId){
-    return area.contains(boxId);
-  }
+}
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/util/DistanceUnit.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/util/DistanceUnit.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/util/DistanceUnit.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,87 @@
+/**
+ * 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.util;
+
+/**
+ * Enum representing difference distance units, currently only kilometers and miles
+ *
+ * <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 enum DistanceUnit {
+
+  MILES("miles"),
+  KILOMETERS("km");
+
+  private static final double MILES_KILOMETRES_RATIO = 1.609344;
+
+  private String unit;
+
+  /**
+   * Creates a new DistanceUnit that represents the given unit
+   *
+   * @param unit Distance unit in String form
+   */
+  DistanceUnit(String unit) {
+    this.unit = unit;
+  }
+
+  /**
+   * Returns the DistanceUnit which represents the given unit
+   *
+   * @param unit Unit whose DistanceUnit should be found
+   * @return DistanceUnit representing the unit
+   * @throws IllegalArgumentException if no DistanceUnit which represents the given unit is found
+   */
+  public static DistanceUnit findDistanceUnit(String unit) {
+    if (MILES.getUnit().equals(unit)) {
+      return MILES;
+    }
+
+    if (KILOMETERS.getUnit().equals(unit)) {
+      return KILOMETERS;
+    }
+
+    throw new IllegalArgumentException("Unknown distance unit " + unit);
+  }
+
+  /**
+   * Converts the given distance from the given DistanceUnit, to the given DistanceUnit
+   *
+   * @param distance Distance to convert
+   * @param from Unit to convert the distance from
+   * @param to Unit of distance to convert to
+   * @return Given distance converted to the distance in the given uni
+   */
+  public static double convert(double distance, DistanceUnit from, DistanceUnit to) {
+    if (from == to) {
+      return distance;
+    }
+    return (to == MILES) ? distance / MILES_KILOMETRES_RATIO : distance * MILES_KILOMETRES_RATIO;
+  }
+
+  // =============================================== Getters / Setters ===============================================
+
+  /**
+   * Returns the string representation of the distance unit
+   *
+   * @return String representation of the distance unit
+   */
+  public String getUnit() {
+    return unit;
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java	(revision 810951)
@@ -1,28 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- 
-package org.apache.lucene.spatial.tier.projections;
-
-/**
- * <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 interface IProjector {
-  public String coordsAsString(double latitude, double longitude);
-  public double[] coords(double latitude, double longitude);
-}
\ No newline at end of file
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java	(revision 833867)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java	(revision 833867)
@@ -1,130 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geohash;
-
-import java.io.IOException;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.DocIdSet;
-import org.apache.lucene.search.FilteredDocIdSet;
-import org.apache.lucene.spatial.tier.DistanceFilter;
-import org.apache.lucene.spatial.tier.DistanceUtils;
-
-
-/** <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 GeoHashDistanceFilter extends DistanceFilter {
-
-  /**
-   * 
-   */
-  private static final long serialVersionUID = 1L;
-  
-  private double lat;
-  private double lng;
-  private String geoHashField;
-  
-  /**
-   * Provide a distance filter based from a center point with a radius
-   * in miles
-   * @param startingFilter
-   * @param lat
-   * @param lng
-   * @param miles
-   */
-  public GeoHashDistanceFilter(Filter startingFilter, double lat, double lng, double miles, String geoHashField) {
-    super(startingFilter, miles);
-    this.lat = lat;
-    this.lng = lng;
-    this.geoHashField = geoHashField;
-  }
-
-  @Override
-  public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
-
-    final String[] geoHashValues = FieldCache.DEFAULT.getStrings(reader, geoHashField);
-
-    final int docBase = nextDocBase;
-    nextDocBase += reader.maxDoc();
-
-    return new FilteredDocIdSet(startingFilter.getDocIdSet(reader)) {
-      @Override
-      public boolean match(int doc) {
-        
-        String geoHash = geoHashValues[doc];
-        double[] coords = GeoHashUtils.decode(geoHash);
-        double x = coords[0];
-        double y = coords[1];
-      
-        // round off lat / longs if necessary
-        //      x = DistanceHandler.getPrecision(x, precise);
-        //      y = DistanceHandler.getPrecision(y, precise);
-        Double cachedDistance = distanceLookupCache.get(geoHash);
-        double d;
-      
-        if (cachedDistance != null) {
-          d = cachedDistance.doubleValue();
-        } else {
-          d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
-          distanceLookupCache.put(geoHash, d);
-        }
-
-        if (d < distance){
-          distances.put(doc+docBase, d);
-          return true;
-        } else {
-          return false;
-        }
-      }
-    };
-  }
-
-  /** Returns true if <code>o</code> is equal to this. */
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof GeoHashDistanceFilter)) return false;
-    GeoHashDistanceFilter other = (GeoHashDistanceFilter) o;
-
-    if (!this.startingFilter.equals(other.startingFilter) ||
-        this.distance != other.distance ||
-        this.lat != other.lat ||
-        this.lng != other.lng ||
-        !this.geoHashField.equals(other.geoHashField) ) {
-      return false;
-    }
-    return true;
-  }
-
-  /** Returns a hash code value for this object.*/
-  @Override
-  public int hashCode() {
-    int h = Double.valueOf(distance).hashCode();
-    h ^= startingFilter.hashCode();
-    h ^= Double.valueOf(lat).hashCode();
-    h ^= Double.valueOf(lng).hashCode();
-    h ^= geoHashField.hashCode();
-    
-    return h;
-  }
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java	(revision 810951)
@@ -1,28 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry;
-
-/**
- * <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 enum DistanceUnits {
-  MILES,
-  KILOMETERS;
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java	(revision 833867)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java	(revision 833867)
@@ -1,136 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.tier;
-
-import java.io.IOException;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FilteredDocIdSet;
-import org.apache.lucene.search.FieldCache;
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.DocIdSet;
-
-
-/**
- * <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 LatLongDistanceFilter extends DistanceFilter {
-
-  /**
-   * 
-   */
-  private static final long serialVersionUID = 1L;
-  
-  double lat;
-  double lng;
-  String latField;
-  String lngField;
-
-  int nextOffset = 0;
-  
-  /**
-   * Provide a distance filter based from a center point with a radius
-   * in miles.
-   * @param startingFilter Filter to start from
-   * @param lat
-   * @param lng
-   * @param miles
-   * @param latField
-   * @param lngField
-   */
-  public LatLongDistanceFilter(Filter startingFilter, double lat, double lng, double miles, String latField, String lngField) {
-    super(startingFilter, miles);
-    this.lat = lat;
-    this.lng = lng;
-    this.latField = latField;
-    this.lngField = lngField;
-  }
-  
-  @Override
-  public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
-
-    final double[] latIndex = FieldCache.DEFAULT.getDoubles(reader, latField);
-    final double[] lngIndex = FieldCache.DEFAULT.getDoubles(reader, lngField);
-
-    final int docBase = nextDocBase;
-    nextDocBase += reader.maxDoc();
-
-    return new FilteredDocIdSet(startingFilter.getDocIdSet(reader)) {
-      @Override
-      protected boolean match(int doc) {
-        double x = latIndex[doc];
-        double y = lngIndex[doc];
-      
-        // round off lat / longs if necessary
-        //      x = DistanceHandler.getPrecision(x, precise);
-        //      y = DistanceHandler.getPrecision(y, precise);
-      
-        String ck = Double.toString(x)+","+Double.toString(y);
-        Double cachedDistance = distanceLookupCache.get(ck);
-
-        double d;
-        if (cachedDistance != null){
-          d = cachedDistance.doubleValue();
-        } else {
-          d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
-          distanceLookupCache.put(ck, d);
-        }
-
-        if (d < distance) {
-          // Save distances, so they can be pulled for
-          // sorting after filtering is done:
-          distances.put(doc+docBase, d);
-          return true;
-        } else {
-          return false;
-        }
-      }
-    };
-  }
-
-  /** Returns true if <code>o</code> is equal to this. */
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof LatLongDistanceFilter)) return false;
-    LatLongDistanceFilter other = (LatLongDistanceFilter) o;
-
-    if (!this.startingFilter.equals(other.startingFilter) ||
-        this.distance != other.distance ||
-        this.lat != other.lat ||
-        this.lng != other.lng ||
-        !this.latField.equals(other.latField) ||
-        !this.lngField.equals(other.lngField)) {
-      return false;
-    }
-    return true;
-  }
-
-  /** Returns a hash code value for this object.*/
-  @Override
-  public int hashCode() {
-    int h = Double.valueOf(distance).hashCode();
-    h ^= startingFilter.hashCode();
-    h ^= Double.valueOf(lat).hashCode();
-    h ^= Double.valueOf(lng).hashCode();
-    h ^= latField.hashCode();
-    h ^= lngField.hashCode();
-    return h;
-  }
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java	(revision 834847)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java	Wed Dec 09 16:23:54 CET 2009
@@ -14,48 +14,58 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.lucene.spatial.tier;
 
-import java.io.IOException;
-import java.util.List;
-
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermDocs;
-import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.Filter;
 import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.util.OpenBitSet;
 
+import java.io.IOException;
+import java.util.List;
+
 /**
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * CartesianShapeFilter is a proper Lucene filter that filters out documents that are not within the boxes that define
+ * a certain CartesianShape overlaps.  For example, if a shape overlaps boxes 1, 3 and 6, then a document that has been
+ * plotted to be within box 5 will be filtered out.
+ *
+ * <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 CartesianShapeFilter extends Filter {
- 
+
-  private final Shape shape;
-  private final String fieldName;
+  private CartesianShape cartesianShape;
+  private String fieldName;
-  
+
-  CartesianShapeFilter(final Shape shape, final String fieldName){
-    this.shape = shape;
+  /**
+   * Creates a new CartesianShapeFilter that will filter out documents that are not within the boxes defined in the
+   * given CartesianShape.  The Filter will check the values of the given field to see whether a document is within a
+   * certain box.
+   *
+   * @param cartesianShape CartesianShape containing boxes that documents must be within, in order not to be filtered out
+   * @param fieldName Name of the field in the documents that will be checked for what boxes the document is in
+   */
+  public CartesianShapeFilter(CartesianShape cartesianShape, String fieldName) {
+    this.cartesianShape = cartesianShape;
     this.fieldName = fieldName;
   }
-  
+
+  /**
+   * {@inheritDoc}
+   */
   @Override
-  public DocIdSet getDocIdSet(final IndexReader reader) throws IOException {
-    final OpenBitSet bits = new OpenBitSet(reader.maxDoc());
-    final TermDocs termDocs = reader.termDocs();
-    final List<Double> area = shape.getArea();
-    int sz = area.size();
+  public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+    OpenBitSet bits = new OpenBitSet(reader.maxDoc());
-    
+
-    final Term term = new Term(fieldName);
-    // iterate through each boxid
-    for (int i =0; i< sz; i++) {
-      double boxId = area.get(i).doubleValue();
-      termDocs.seek(term.createTerm(NumericUtils.doubleToPrefixCoded(boxId)));
-      // iterate through all documents
-      // which have this boxId
+    TermDocs termDocs = reader.termDocs();
+    List<Double> area = cartesianShape.getBoxIds();
+    for (double boxId : area) {
+      termDocs.seek(new Term(fieldName, NumericUtils.doubleToPrefixCoded(boxId)));
+
+      // iterate through all documents which have this boxId
       while (termDocs.next()) {
         bits.fastSet(termDocs.doc());
       }
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java	(revision 817456)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java	(revision 817456)
@@ -1,191 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-import org.apache.lucene.spatial.geometry.FloatLatLng;
-import org.apache.lucene.spatial.geometry.LatLng;
-
-
-
-/**
- * Lat-long rect.  Instances are mutable.
- *
- * <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 LLRect {
-  private LatLng ll, ur;
-  
-  public LLRect(LatLng ll, LatLng ur) {
-    this.ll=ll;
-    this.ur=ur;
-  }
-  
-  public LLRect(LLRect other) {
-    this.ll=other.ll;
-    this.ur=other.ur;
-  }
-  
-  /**
-   * Return the area in units of lat-lng squared.  This is a contrived unit
-   * that only has value when comparing to something else.
-   */
-  public double area() {
-    return Math.abs((ll.getLat()-ur.getLat()) * (ll.getLng()-ur.getLng()));
-  }
-
-  public LatLng getLowerLeft() {
-    return ll;
-  }
-  
-  public LatLng getUpperRight() {
-    return ur;
-  }
-  
-  @Override
-  public String toString() {
-    return "{" + ll + ", " + ur + "}";
-  }
-
-  public LatLng getMidpoint() {
-    return ll.calculateMidpoint(ur);
-  }
-
-  /**
-   * Approximates a box centered at the given point with the given width and height in miles.
-   * @param center
-   * @param widthMi
-   * @param heightMi
-   */
-  public static LLRect createBox(LatLng center, double widthMi, double heightMi) {
-    double d = widthMi;
-    LatLng ur = boxCorners(center, d, 45.0); // assume right angles
-    LatLng ll = boxCorners(center, d, 225.0);
-
-    //System.err.println("boxCorners: ur " + ur.getLat() + ',' + ur.getLng());
-    //System.err.println("boxCorners: cnt " + center.getLat() + ',' + center.getLng());
-    //System.err.println("boxCorners: ll " + ll.getLat() + ',' + ll.getLng());
-    return new LLRect(ll, ur);
-  }
-  
-  /**
-   * Returns a rectangle shape for the bounding box
-   */
-  public Rectangle toRectangle() {
-    return new Rectangle(ll.getLng(), ll.getLat(), ur.getLng(), ur.getLat());
-  }
-
-  private static LatLng boxCorners(LatLng center, double d, double brngdeg) {
-    double a = center.getLat();
-    double b = center.getLng();
-    double R = 3963.0; // radius of earth in miles
-    double brng = (Math.PI*brngdeg/180);
-    double lat1 = (Math.PI*a/180);
-    double lon1 = (Math.PI*b/180);
-
-    // Haversine formula
-    double lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
-                             Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
-    double lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
-                                    Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
-
-    lat2 = (lat2*180)/Math.PI;
-    lon2 = (lon2*180)/Math.PI;
-
-    // normalize long first
-    LatLng ll = normLng(lat2,lon2);
-
-    // normalize lat - could flip poles
-    ll = normLat(ll.getLat(),ll.getLng());
-
-    return ll;
-}
-
-  /**
-   * Returns a normalized Lat rectangle shape for the bounding box
-   * If you go over the poles, you need to flip the lng value too
-   */
-  private static LatLng normLat(double lat, double lng) {
-    if (lat > 90.0) {
-        lat = 90.0 - (lat - 90.0);
-        if (lng < 0) {
-                lng = lng+180;
-        } else {
-                lng=lng-180;
-        }
-    }
-    else if (lat < -90.0) {
-        lat = -90.0 - (lat + 90.0);
-        if (lng < 0) {
-                lng = lng+180;
-        } else {
-                lng=lng-180;
-        }
-    }
-    LatLng ll=new FloatLatLng(lat, lng);
-    return ll;
-  }
-
-  /**
-   * Returns a normalized Lng rectangle shape for the bounding box
-   */
-  private static LatLng normLng(double lat,double lng) {
-    if (lng > 180.0) {
-        lng = -1.0*(180.0 - (lng - 180.0));
-    }
-    else if (lng < -180.0) {
-        lng = (lng + 180.0)+180.0;
-    }
-    LatLng ll=new FloatLatLng(lat, lng);
-    return ll;
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = 1;
-    result = prime * result + ((ll == null) ? 0 : ll.hashCode());
-    result = prime * result + ((ur == null) ? 0 : ur.hashCode());
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (obj == null)
-      return false;
-    if (getClass() != obj.getClass())
-      return false;
-    LLRect other = (LLRect) obj;
-    if (ll == null) {
-      if (other.ll != null)
-        return false;
-    } else if (!ll.equals(other.ll))
-      return false;
-    if (ur == null) {
-      if (other.ur != null)
-        return false;
-    } else if (!ur.equals(other.ur))
-      return false;
-    return true;
-  }
-  
-  
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geohash/package.html
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geohash/package.html	(revision 810923)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geohash/package.html	(revision 810923)
@@ -1,22 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<html>
-<body>
-Support for <a href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> encoding, decoding, and filtering.
-</body>
-</html>
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFilter.java	(revision 833867)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/DistanceFilter.java	Wed Dec 09 16:23:54 CET 2009
@@ -14,98 +14,49 @@
  * limitations under the License.
  */
 
-package org.apache.lucene.spatial.tier;
+package org.apache.lucene.spatial.distance;
 
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.HashMap;
+import org.apache.lucene.index.IndexReader;
+
 import java.io.IOException;
-import java.io.ObjectInputStream;
+import java.util.BitSet;
+import java.util.Map;
 
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.spatial.tier.DistanceHandler.Precision;
+/**
+ * DistanceFilter is responsible for filtering out documents from an existing BitSet, based on their calculated distance
+ * from the central point.  Because the costing of calculating distances for documents is relatively high, this filter
+ * uses an existing BitSet, which will have been created another filter previously.  As such, this is technicall not
+ * a Lucene Filter.
+ * <p/>
+ * In addition to filtering out documents, the filter also holds onto the calculated distances so they can be used after
+ * the filtering process.
+ *
+ * <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 interface DistanceFilter {
 
-/**
+  /**
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+   * Returns a map of calculated distances by document ids
+   *
+   * @return Map of calculated distances by document ids
- */
+   */
-public abstract class DistanceFilter extends Filter {
+  Map<Integer, Double> getDistances();
 
-  final protected Filter startingFilter;
-  protected Precision precise;
-  protected Map<Integer,Double> distances;
-  protected double distance;
+  /**
+   * Returns the calculated distance for a document with the given id
+   *
+   * @param docId ID of the document whose distance is to be returned
+   * @return Calculated distance of the document with the id
+   */
+  Double getDistance(int docId);
 
-  protected int nextDocBase; 
-  protected transient WeakHashMap<String,Double> distanceLookupCache;
-
-  /** Filters the startingFilter by precise distance
-   *  checking filter */
-  public DistanceFilter(Filter startingFilter, double distance) {
-    if (startingFilter == null) {
-      throw new IllegalArgumentException("please provide a non-null startingFilter; you can use QueryWrapperFilter(MatchAllDocsQuery) as a no-op filter");
-    }
-    this.startingFilter = startingFilter;
-    this.distance = distance;
-
-    // NOTE: neither of the distance filters use precision
-    // now - if we turn that on, we'll need to pass top
-    // reader into here
-    // setPrecision(reader.maxDoc());
-
-    /* store calculated distances for reuse by other components */
-    distances = new HashMap<Integer,Double>();
-
-    // create an intermediate cache to avoid recomputing
-    //   distances for the same point 
-    //   TODO: Why is this a WeakHashMap? 
-    distanceLookupCache = new WeakHashMap<String,Double>();
-  }
-
-  /** needed for deserialization, because the cache is transient */
-  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
-    stream.defaultReadObject();
-    distanceLookupCache = new WeakHashMap<String,Double>();
-  }
-
-  public Map<Integer,Double> getDistances(){
-    return distances;
-  }
-  
-  public Double getDistance(int docid){
-    return distances.get(docid);
-  }
-  
-  public void setDistances(Map<Integer, Double> distances) {
-    this.distances = distances;
-  }
-
-  /** You must call this before re-using this DistanceFilter
-   *  across searches */
-  public void reset() {
-    nextDocBase = 0;
-  }
-
-  /** Returns true if <code>o</code> is equal to this. */
-  @Override
-  public abstract boolean equals(Object o);
-
-  /** Returns a hash code value for this object.*/
-  @Override
-  public abstract int hashCode();
-
-  /*
-  private void setPrecision(int maxDocs) {
-    precise = Precision.EXACT;
-    
-    if (maxDocs > 1000 && distance > 10) {
-      precise = Precision.TWENTYFEET;
-    }
-    
-    if (maxDocs > 10000 && distance > 10){
-      precise = Precision.TWOHUNDREDFEET;
-    }
-  }
+  /**
+   * Filters the documents from the given IndexReader who have bits set in the given BitSet.
+   *
+   * @param reader IndexReader from where the documents will be read from
+   * @param bits BitSet containing bits indicating which documents should be considered to be filtered out
+   * @return BitSet with bits set representing those documents that passed the filter
+   * @throws java.io.IOException Can be thrown while reading from the IndexReader
-  */
+   */
+  BitSet bits(IndexReader reader, BitSet bits) throws IOException;
 }
Index: contrib/spatial/src/java/org/apache/lucene/spatial/util/SpatialConstants.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/util/SpatialConstants.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/util/SpatialConstants.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,46 @@
+/**
+ * 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.util;
+
+/**
+ * Constants, such as the radius of Earth, that should be consistent across all classes
+ *
+ * <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 interface SpatialConstants {
+
+  /**
+   * Average Earth Radius in kilometres src: http://en.wikipedia.org/wiki/Earth_radius
+   */
+  final double EARTH_RADIUS_KILOMETRES = 6371;
+
+  /**
+   * Average Earth Radius in miles src: http://en.wikipedia.org/wiki/Earth_radius
+   */
+  final double EARTH_RADIUS_MILES = 3959;
+
+  /**
+   * Circumference of the Earth at the equator in kilometres src: http://www.lyberty.com/encyc/articles/earth.html
+   */
+  final double EARTH_CIRCUMFERENCE_KILOMETRES = 40076;
+
+  /**
+   * Circumference of the Earth at the equator in miles src: http://www.lyberty.com/encyc/articles/earth.html
+   */
+  final double EARTH_CIRCUMFERENCE_MILES = 24902;
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestArcDistanceCalculator.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestArcDistanceCalculator.java	Wed Dec 09 19:30:08 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestArcDistanceCalculator.java	Wed Dec 09 19:30:08 CET 2009
@@ -0,0 +1,43 @@
+/**
+ * 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.geometry;
+
+import junit.framework.TestCase;
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.geometry.ArcDistanceCalculator}
+ */
+public class TestArcDistanceCalculator extends TestCase {
+
+  /**
+   * Pass condition: The distance calculated in miles matches the expected distance
+   */
+  public void testCalculate_miles() {
+    double distance = new ArcDistanceCalculator().calculate(53.85, 4.51, 43.22, 9.68, DistanceUnit.MILES);
+    assertEquals(771.958067752198, distance, 0D);
+  }
+
+  /**
+   * Pass condition: The distance calculated in kilometers matches the expected distance
+   */
+  public void testCalculate_kilometers() {
+    double distance = new ArcDistanceCalculator().calculate(53.85, 4.51, 43.22, 9.68, DistanceUnit.KILOMETERS);
+    assertEquals(1242.3460845885934, distance, 0D);
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java	(revision 812248)
@@ -1,143 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry;
-
-/**
- * <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 FloatLatLng extends LatLng {
-  private double lat;
-  private double lng;
-  private boolean normalized;
-  
-  public FloatLatLng(double lat, double lng) {
-    if (lat>90.0 || lat<-90.0) throw new IllegalArgumentException("Illegal latitude value " + lat);
-    this.lat=lat;
-    this.lng=lng;
-  }
-  
-  public FloatLatLng(LatLng ll) {
-    this.lat=ll.getLat();
-    this.lng=ll.getLng();
-  }
-  
-  @Override
-  public LatLng copy() {
-    return new FloatLatLng(this);
-  }
-
-  @Override
-  public int getFixedLat() {
-    return FixedLatLng.doubleToFixed(this.lat);
-  }
-
-  @Override
-  public int getFixedLng() {
-    return FixedLatLng.doubleToFixed(this.lng);
-  }
-
-  @Override
-  public double getLat() {
-    return this.lat;
-  }
-
-  @Override
-  public double getLng() {
-    return this.lng;
-  }
-
-  @Override
-  public boolean isFixedPoint() {
-    return false;
-  }
-
-  @Override
-  public FixedLatLng toFixed() {
-    return new FixedLatLng(this);
-  }
-
-  @Override
-  public FloatLatLng toFloat() {
-    return this;
-  }
-  
-  @Override
-  public boolean isNormalized() {
-    return 
-      normalized || (
-          (lng>=-180) &&
-          (lng<=180)
-          );
-  }
-
-  @Override
-  public LatLng normalize() {
-    if (isNormalized()) return this;
-    
-    double delta=0;
-    if (lng<0) delta=360;
-    if (lng>=0) delta=-360;
-    
-    double newLng=lng;
-    while (newLng<=-180 || newLng>=180) {
-      newLng+=delta;
-    }
-    
-    FloatLatLng ret=new FloatLatLng(lat, newLng);
-    ret.normalized=true;
-    return ret;
-  }
-
-  @Override
-  public LatLng calculateMidpoint(LatLng other) {
-    return new FloatLatLng(
-        (lat+other.getLat())/2.0,
-        (lng+other.getLng())/2.0);
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    long temp;
-    temp = Double.doubleToLongBits(lat);
-    int result = prime  + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(lng);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    result = prime * result + (normalized ? 1231 : 1237);
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (getClass() != obj.getClass())
-      return false;
-    FloatLatLng other = (FloatLatLng) obj;
-    if (Double.doubleToLongBits(lat) != Double.doubleToLongBits(other.lat))
-      return false;
-    if (Double.doubleToLongBits(lng) != Double.doubleToLongBits(other.lng))
-      return false;
-    if (normalized != other.normalized)
-      return false;
-    return true;
-  }
-
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java	(revision 811070)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/projection/SinusoidalProjector.java	Wed Dec 09 16:23:54 CET 2009
@@ -15,30 +15,34 @@
  * limitations under the License.
  */
 
-package org.apache.lucene.spatial.tier.projections;
+package org.apache.lucene.spatial.tier.projection;
 
 /**
  * Based on Sinusoidal Projections
- * Project a latitude / longitude on a 2D cartesian map
+ * Project a latitude / longitude on a 2D cartisian map
  *
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * <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 SinusoidalProjector implements IProjector {
+public class SinusoidalProjector implements Projector {
 
+  private static final double LATITUDE_RANGE = Math.PI;
+  private static final double LONGITUDE_RANGE = 2 * Math.PI;
+  private static final double[] RANGE = new double[]{LATITUDE_RANGE, LONGITUDE_RANGE};
-  
+
-  public String coordsAsString(double latitude, double longitude) {
-    return null;
-  }
-  
+  /**
+   * {@inheritDoc}
+   */
   public double[] coords(double latitude, double longitude) {
     double rlat = Math.toRadians(latitude);
     double rlong = Math.toRadians(longitude);
     double nlat = rlong * Math.cos(rlat);
-    double r[] = {nlat, rlong};
-    return r;
-    
+    return new double[]{nlat, rlong};
   }
-  
+
+  /**
+   * {@inheritDoc}
+   */
+  public double[] range() {
+    return RANGE;
-}
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java	(revision 810951)
@@ -1,35 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.tier;
-
-/**
- * <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 InvalidGeoException extends Exception {
-
-  /**
-   * 
-   */
-  private static final long serialVersionUID = 1L;
-
-  public InvalidGeoException(String message){
-    super(message);
-  }
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java	(revision 810951)
@@ -1,31 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-/**
- *
- * <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 enum IntersectCase {
-  WITHIN,
-  CONTAINS,
-  OUTSIDE,
-  INTERSECTS;
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/util/TestGeoHashUtils.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/util/TestGeoHashUtils.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/util/TestGeoHashUtils.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,62 @@
+/**
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.util.GeoHashUtils}
+ */
+public class TestGeoHashUtils extends TestCase {
+
+  /**
+   * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96", lat=57.64911 lng=10.40744 should be encoded
+   * as "u4pruydqqvj8"
+   */
+  public void testEncode() {
+    String hash = GeoHashUtils.encode(42.6, -5.6);
+    assertEquals("ezs42e44yx96", hash);
+
+    hash = GeoHashUtils.encode(57.64911, 10.40744);
+    assertEquals("u4pruydqqvj8", hash);
+  }
+
+  /**
+   * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then decoded within 0.00001 of the original value
+   */
+  public void testDecode_preciseLongitudeLatitude() {
+    String hash = GeoHashUtils.encode(52.3738007, 4.8909347);
+
+    double[] latitudeLongitude = GeoHashUtils.decode(hash);
+
+    assertEquals(52.3738007, latitudeLongitude[0], 0.00001D);
+    assertEquals(4.8909347, latitudeLongitude[1], 0.00001D);
+  }
+
+  /**
+   * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded within 0.00001 of the original value
+   */
+  public void testDecode_impreciseLongitudeLatitude() {
+    String hash = GeoHashUtils.encode(84.6, 10.5);
+
+    double[] latitudeLongitude = GeoHashUtils.decode(hash);
+
+    assertEquals(84.6, latitudeLongitude[0], 0.00001D);
+    assertEquals(10.5, latitudeLongitude[1], 0.00001D);
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java	(revision 812248)
@@ -1,160 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry;
-
-/**
- * <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 FixedLatLng extends LatLng {
-  public static final double SCALE_FACTOR=1000000;
-  public static final int SCALE_FACTOR_INT=1000000;
-  
-  private int lat, lng;
-  private boolean normalized;
-  
-  public FixedLatLng(int lat, int lng) {
-    setLat(lat);
-    setLng(lng);
-  }
-  
-  public FixedLatLng(LatLng ll) {
-    this.lat=ll.getFixedLat();
-    this.lng=ll.getFixedLng();
-  }
-  
-  protected void setLat(int lat) {
-    if (lat>90*SCALE_FACTOR || lat<-90*SCALE_FACTOR) {
-      throw new IllegalArgumentException("Illegal lattitude");
-    }
-    this.lat=lat;
-  }
-
-  protected void setLng(int lng) {
-    this.lng=lng;
-  }
-  
-  public static double fixedToDouble(int fixed) {
-    return (fixed)/SCALE_FACTOR;
-  }
-  
-  public static int doubleToFixed(double d) {
-    return (int)(d*SCALE_FACTOR);
-  }
-  
-  @Override
-  public LatLng copy() {
-    return new FixedLatLng(this);
-  }
-
-  @Override
-  public int getFixedLat() {
-    return lat;
-  }
-
-  @Override
-  public int getFixedLng() {
-    return lng;
-  }
-
-  @Override
-  public double getLat() {
-    return fixedToDouble(lat);
-  }
-
-  @Override
-  public double getLng() {
-    return fixedToDouble(lng);
-  }
-
-  @Override
-  public boolean isFixedPoint() {
-    return true;
-  }
-
-  @Override
-  public FixedLatLng toFixed() {
-    return this;
-  }
-
-  @Override
-  public FloatLatLng toFloat() {
-    return new FloatLatLng(this);
-  }
-
-  @Override
-  public boolean isNormalized() {
-    return 
-      normalized || (
-          (lng>=-180*SCALE_FACTOR_INT) &&
-          (lng<=180*SCALE_FACTOR_INT)
-          );
-  }
-
-  @Override
-  public LatLng normalize() {
-    if (isNormalized()) return this;
-    
-    int delta=0;
-    if (lng<0) delta=360*SCALE_FACTOR_INT;
-    if (lng>=0) delta=-360*SCALE_FACTOR_INT;
-    
-    int newLng=lng;
-    while (newLng<=-180*SCALE_FACTOR_INT || newLng>=180*SCALE_FACTOR_INT) {
-      newLng+=delta;
-    }
-    
-    FixedLatLng ret=new FixedLatLng(lat, newLng);
-    ret.normalized=true;
-    return ret;
-  }
-  
-  @Override
-  public LatLng calculateMidpoint(LatLng other) {
-    return new FixedLatLng(
-        (lat+other.getFixedLat())/2,
-        (lng+other.getFixedLng())/2);
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = prime + lat;
-    result = prime * result + lng;
-    result = prime * result + (normalized ? 1231 : 1237);
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (getClass() != obj.getClass())
-      return false;
-    FixedLatLng other = (FixedLatLng) obj;
-    if (lat != other.lat)
-      return false;
-    if (lng != other.lng)
-      return false;
-    if (normalized != other.normalized)
-      return false;
-    return true;
-  }
-  
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java	(revision 812248)
@@ -1,83 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry;
-
-/**
- * Represents lat/lngs as fixed point numbers translated so that all
- * world coordinates are in the first quadrant.  The same fixed point
- * scale as is used for FixedLatLng is employed.
- *
- * <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 CartesianPoint {
-  private int x;
-  private int y;
-  
-  public CartesianPoint(int x, int y) {
-    this.x=x;
-    this.y=y;
-  }
-  
-  public int getX() {
-    return x;
-  }
-  
-  public int getY() {
-    return y;
-  }
-  
-  @Override
-  public String toString() {
-    return "Point(" + x + "," + y + ")";
-  }
-
-  /**
-   * Return a new point translated in the x and y dimensions
-   */
-  public CartesianPoint translate(int deltaX, int deltaY) {
-    return new CartesianPoint(this.x+deltaX, this.y+deltaY);
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = 1;
-    result = prime * result + x;
-    result = prime * result + y;
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (obj == null)
-      return false;
-    if (getClass() != obj.getClass())
-      return false;
-    CartesianPoint other = (CartesianPoint) obj;
-    if (x != other.x)
-      return false;
-    if (y != other.y)
-      return false;
-    return true;
-  }
-  
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java	(revision 810951)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java	(revision 810951)
@@ -1,57 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-
-/**
- * Common set of operations available on 2d shapes.
- *
- * <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 interface Geometry2D {
-  /**
-   * Translate according to the vector
-   * @param v
-   */
-  public void translate(Vector2D v);
-  
-  /**
-   * Does the shape contain the given point
-   * @param p
-   */
-  public boolean contains(Point2D p);
-  
-  /**
-   * Return the area
-   */
-  public double area();
-  
-  /**
-   * Return the centroid
-   */
-  public Point2D centroid();
-  
-  /**
-   * Returns information about how this shape intersects the given rectangle
-   * @param r
-   */
-  public IntersectCase intersect(Rectangle r);
-
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/distance/LatLongLocationDataSetFactory.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/distance/LatLongLocationDataSetFactory.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/distance/LatLongLocationDataSetFactory.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,88 @@
+/**
+ * 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.distance;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.spatial.geometry.Point;
+
+import java.io.IOException;
+
+/**
+ * Implementation of {@link LocationDataSetFactory} that builds LocationDataSet based on 2 fields representing latitude
+ * and longitude.
+ *
+ * <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 LatLongLocationDataSetFactory implements LocationDataSetFactory {
+
+  private final String latField;
+  private final String lngField;
+
+  /**
+   * Creates a new LatLongLocationDataSetFactory that will use the latitude and longitude data read from the fields
+   * with the given names
+   *
+   * @param latField Name of the latitude field
+   * @param lngField Name of the longitude field
+   */
+  public LatLongLocationDataSetFactory(String latField, String lngField) {
+    this.latField = latField;
+    this.lngField = lngField;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public LocationDataSet buildLocationDataSet(IndexReader indexReader) throws IOException {
+    return new LatLongLocationDataSet(
+        FieldCache.DEFAULT.getDoubles(indexReader, latField, FieldCache.NUMERIC_UTILS_DOUBLE_PARSER),
+        FieldCache.DEFAULT.getDoubles(indexReader, lngField, FieldCache.NUMERIC_UTILS_DOUBLE_PARSER));
+  }
+
+  // ================================================= Inner Classes =================================================
+
+  /**
+   * Implementation of LocationDataSet that uses fields that represent latitude and longitude to construct the Point
+   * for a document.
+   */
+  private class LatLongLocationDataSet implements LocationDataSet {
+
+    private double[] latIndex;
+    private double[] lngIndex;
+
+    /**
+     * Creates a new LatLongLocationDataSet which uses the values in the given latitude and longitude indexes to create
+     * Points for documents
+     *
+     * @param latIndex Array containing the latitude field values taken from the index
+     * @param lngIndex Array containing the longitude field values taken from the index
+     */
+    private LatLongLocationDataSet(double[] latIndex, double[] lngIndex) {
+      this.latIndex = latIndex;
+      this.lngIndex = lngIndex;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Point getPoint(int docId) {
+      return new Point(latIndex[docId], lngIndex[docId]);
+    }
+  }
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestPlaneGeoDistanceCalculator.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestPlaneGeoDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestPlaneGeoDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,43 @@
+/**
+ * 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.geometry;
+
+import junit.framework.TestCase;
+import org.apache.lucene.spatial.util.DistanceUnit;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.geometry.PlaneGeoDistanceCalculator}
+ */
+public class TestPlaneGeoDistanceCalculator extends TestCase {
+
+  /**
+   * Pass condition: The distance calculated in miles matches the expected distance
+   */
+  public void testCalculate_miles() {
+    double distance = new PlaneGeoDistanceCalculator().calculate(53.85, 4.51, 43.22, 9.68, DistanceUnit.MILES);
+    assertEquals(817.6548750234183, distance, 0D);
+  }
+
+  /**
+   * Pass condition: The distance calculated in kilometers matches the expected distance
+   */
+  public void testCalculate_kilometers() {
+    double distance = new PlaneGeoDistanceCalculator().calculate(53.85, 4.51, 43.22, 9.68, DistanceUnit.KILOMETERS);
+    assertEquals(1315.8879671896882, distance, 0D);
+  }
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java	(revision 787424)
+++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java	(revision 787424)
@@ -1,49 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.spatial.tier;
-
-import java.text.DecimalFormat;
-
-
-public class DistanceCheck {
-
-  /**
-   * @param args
-   */
-  public static void main(String[] args) {
-    double lat1 = 0;
-    double long1 = 0;
-    double lat2 = 0;
-    double long2 = 0;
-    
-    for (int i =0; i < 90; i++){
-      double dis = DistanceUtils.getInstance().getDistanceMi(lat1, long1, lat2, long2);
-      lat1 +=1;
-      lat2 = lat1 + 0.001;
-      
-      System.out.println(lat1+","+long1+","+lat2+","+long2+","+formatDistance(dis));
-      
-    }
-
-  }
-
-  public static String formatDistance (Double d){
-    DecimalFormat df1 = new DecimalFormat("####.000000");
-    return df1.format(d);
-  }
-  
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/TestDistanceFieldComparatorSource.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/TestDistanceFieldComparatorSource.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/TestDistanceFieldComparatorSource.java	Wed Dec 09 16:23: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/java/org/apache/lucene/spatial/geometry/PlaneGeoDistanceCalculator.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/PlaneGeoDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/PlaneGeoDistanceCalculator.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,51 @@
+/**
+ * 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.geometry;
+
+import org.apache.lucene.spatial.util.DistanceUnit;
+import org.apache.lucene.spatial.util.SpatialConstants;
+
+/**
+ * Implementation of {@link GeoDistanceCalculator} that assumes that the 2 points are on a flat plane.
+ * <p/>
+ * Obviously since the world is curved, this calculation with have some error.  However in many cases, the bigger the
+ * distance, the less important the error.  The impact of doing such a simple calculation is that the performance of
+ * calculating the distance of millions of points is considerably reduced.
+ *
+ * <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 PlaneGeoDistanceCalculator implements GeoDistanceCalculator {
+
+  private final static double DISTANCE_PER_DEGREE = SpatialConstants.EARTH_CIRCUMFERENCE_MILES / 360;
+
+  /**
+   * {@inheritDoc}
+   */
+  public double calculate(
+      double sourceLatitude,
+      double sourceLongitude,
+      double targetLatitude,
+      double targetLongitude,
+      DistanceUnit unit) {
+
+    double px = targetLongitude - sourceLongitude;
+    double py = targetLatitude - sourceLatitude;
+    double distanceMiles = Math.sqrt(px * px + py * py) * DISTANCE_PER_DEGREE;
+    return DistanceUnit.convert(distanceMiles, DistanceUnit.MILES, unit);
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Point2D.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Point2D.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/Point.java	Wed Dec 09 16:23:54 CET 2009
@@ -15,123 +15,46 @@
  * limitations under the License.
  */
 
-package org.apache.lucene.spatial.geometry.shape;
+package org.apache.lucene.spatial.geometry;
 
-
 /**
- * Point class.  This type is mutable.
+ * Class representing a point consisting simply of an x and y coordinate
  *
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * <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 Point2D {
-  private double x;
-  private double y;
+public class Point {
 
-  public Point2D(double x, double y) {
+  private final double x;
+  private final double y;
+
+  /**
+   * Constructs a new point consisting of the given x and y coordinates
+   *
+   * @param x x coordinate of point
+   * @param y y coordinate of point
+   */
+  public Point(double x, double y) {
-    this.x=x;
+    this.x = x;
-    this.y=y;
+    this.y = y;
   }
-  
+
-  public Point2D() {
-    this.x=0;
-    this.y=0;
-  }
+  // =============================================== Getters / Setters ===============================================
-  
+
-  public Point2D(Point2D other) {
-    this.x=other.x;
-    this.y=other.y;
-  }
-  
-  @Override
-  public String toString() {
-    return "(" + x + "," + y + ")";
-  }
-  
+  /**
+   * Returns the x coordinate
+   *
+   * @return x coordinate
+   */
   public double getX() {
     return x;
   }
-  
+
+  /**
+   * Returns the y coordinate
+   *
+   * @return y coordinate
+   */
   public double getY() {
     return y;
   }
-  
-  public double x() {
-    return x;
-  }
+}
-
-  public double y() {
-    return y;
-  }
-
-  public void x(double x) {
-    this.x=x;
-  }
-
-  public void y(double y) {
-    this.y=y;
-  }
-
-  public void setX(double x) {
-    this.x = x;
-  }
-  
-  public void setY(double y) {
-    this.y = y;
-  }
-  
-  public void set(double x, double y) {
-    this.x=x;
-    this.y=y;
-  }
-
-  public void add(Vector2D v) {
-    this.x+=v.getX();
-    this.y+=v.getY();
-  }
-
-  public void set(Point2D p1) {
-    this.x=p1.getX();
-    this.y=p1.getY();
-  }
-
-  public void add(Point2D a) {
-    this.x+=a.getX();
-    this.y+=a.getY();
-  }
-
-  public void set(Vector2D v) {
-    this.x=v.getX();
-    this.y=v.getY();
-  }
-  
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = 1;
-    long temp;
-    temp = Double.doubleToLongBits(x);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(y);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (obj == null)
-      return false;
-    if (getClass() != obj.getClass())
-      return false;
-    Point2D other = (Point2D) obj;
-    if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
-      return false;
-    if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
-      return false;
-    return true;
-  }
-  
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/util/TestDistanceUnit.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/util/TestDistanceUnit.java	Wed Dec 09 16:23:54 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/util/TestDistanceUnit.java	Wed Dec 09 16:23:54 CET 2009
@@ -0,0 +1,59 @@
+/**
+ * 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.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.util.DistanceUnit}
+ */
+public class TestDistanceUnit extends TestCase {
+
+  /**
+   * Pass condition: When finding the DistanceUnit for "km", KILOMETRES is found.  When finding the DistanceUnit for
+   * "miles", MILES is found.
+   */
+  public void testFindDistanceUnit() {
+    assertEquals(DistanceUnit.KILOMETERS, DistanceUnit.findDistanceUnit("km"));
+    assertEquals(DistanceUnit.MILES, DistanceUnit.findDistanceUnit("miles"));
+  }
+
+  /**
+   * Pass condition: Searching for the DistanceUnit of an unknown unit "mls" should throw an IllegalArgumentException.
+   */
+  public void testFindDistanceUnit_unknownUnit() {
+    try {
+      DistanceUnit.findDistanceUnit("mls");
+      assertTrue("IllegalArgumentException should have been thrown", false);
+    } catch (IllegalArgumentException iae) {
+      // Expected
+    }
+  }
+
+  /**
+   * Pass condition: Converting between the same units should not change the value.  Converting from MILES to KILOMETRES
+   * involves multiplying the distance by the ratio, and converting from KILOMETRES to MILES involves
+   * dividing by the ratio
+   */
+  public void testConvert() {
+    assertEquals(10.5, DistanceUnit.convert(10.5, DistanceUnit.MILES, DistanceUnit.MILES), 0D);
+    assertEquals(10.5, DistanceUnit.convert(10.5, DistanceUnit.KILOMETERS, DistanceUnit.KILOMETERS), 0D);
+    assertEquals(10.5 * 1.609344, DistanceUnit.convert(10.5, DistanceUnit.MILES, DistanceUnit.KILOMETERS), 0D);
+    assertEquals(10.5 / 1.609344, DistanceUnit.convert(10.5, DistanceUnit.KILOMETERS, DistanceUnit.MILES), 0D);
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/SpatialFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/SpatialFilter.java	Wed Dec 09 19:29:43 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/SpatialFilter.java	Wed Dec 09 19:29:43 CET 2009
@@ -0,0 +1,257 @@
+/**
+ * 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.GeoDistanceCalculator;
+import org.apache.lucene.spatial.tier.CartesianShapeFilterBuilder;
+import org.apache.lucene.spatial.util.DistanceUnit;
+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;
+    CartesianShapeFilterBuilder shapeFilterBuilder = new CartesianShapeFilterBuilder(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,
+      DistanceUnit 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,
+      DistanceUnit 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,
+      DistanceUnit 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,
+      DistanceUnit 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/tier/PolyShape.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/tier/PolyShape.java	(revision 787424)
+++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/PolyShape.java	(revision 787424)
@@ -1,38 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.spatial.tier;
-
-
-/**
- *
- */
-public class PolyShape {
-
-  private static double lat = 38.969398; 
-  private static double lng= -77.386398;
-  private static int miles = 1000;
-  /**
-   * @param args
-   */
-  public static void main(String[] args) {
-  
-    CartesianPolyFilterBuilder cpf = new CartesianPolyFilterBuilder( "_localTier" );
-    cpf.getBoxShape(lat, lng, miles);
-    
-  }
-
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilterBuilder.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilterBuilder.java	(revision 833122)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilterBuilder.java	Wed Dec 09 16:23:54 CET 2009
@@ -17,143 +17,117 @@
 
 package org.apache.lucene.spatial.tier;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-
 import org.apache.lucene.search.Filter;
-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.spatial.geometry.LatLng;
-import org.apache.lucene.spatial.geometry.FloatLatLng;
-import org.apache.lucene.spatial.geometry.shape.LLRect;
+import org.apache.lucene.spatial.geometry.LatLngRectangle;
+import org.apache.lucene.spatial.tier.projection.Projector;
+import org.apache.lucene.spatial.tier.projection.SinusoidalProjector;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 
+
 /**
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * Builds instances of {@link org.apache.lucene.spatial.tier.CartesianShapeFilter} for a given latitude, longitude and 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 CartesianPolyFilterBuilder {
+public class CartesianShapeFilterBuilder {
 
-  // Finer granularity than 1 mile isn't accurate with
-  // standard java math.  Also, there's already a 2nd
-  // precise filter, if needed, in DistanceQueryBuilder,
-  // that will make the filtering exact.
-  public static final double MILES_FLOOR = 1.0;
+  private static final Projector PROJECTOR = new SinusoidalProjector();
+  private static final double MILES_FLOOR = 1.0;
 
-  private IProjector projector = new SinusoidalProjector();
   private final String tierPrefix;
-  
+
-  public CartesianPolyFilterBuilder( String tierPrefix ) {
+  public CartesianShapeFilterBuilder(String tierPrefix) {
     this.tierPrefix = tierPrefix;
   }
-  
+
-  public Shape getBoxShape(double latitude, double longitude, double miles)
-  {  
+  public Filter buildFilter(double latitude, double longitude, double miles) {
+    CartesianShape cartesianShape = getBoxShape(latitude, longitude, miles);
+    return new CartesianShapeFilter(cartesianShape, tierPrefix + cartesianShape.getTierId());
+  }
+
+  // ================================================ Helper Methods =================================================
+
+  /**
+   * Creates the CartesianShape that covers a circle with the given latitude and longitude at its centre, and the given
+   * radius in miles
+   *
+   * @param latitude Latitude of the centre of the circle
+   * @param longitude Longitude of the centre of the circle
+   * @param miles Radius of the circle
+   * @return CartesianShape that covers the circle defined by the lat/long and radius
+   */
+  private CartesianShape getBoxShape(double latitude, double longitude, double miles) {
     if (miles < MILES_FLOOR) {
       miles = MILES_FLOOR;
     }
-    LLRect box1 = LLRect.createBox( new FloatLatLng( latitude, longitude ), miles, miles );
-    LatLng ll = box1.getLowerLeft();
-    LatLng ur = box1.getUpperRight();
 
-    double latY = ur.getLat();
-    double latX = ll.getLat();
-    double longY = ur.getLng();
-    double longX = ll.getLng();
-    double longX2 = 0.0;
+    LatLngRectangle latLngRectangle = LatLngRectangle.createBox(new LatLng(latitude, longitude), miles);
+    LatLng lowerLeft = latLngRectangle.getLowerLeft();
+    LatLng upperRight = latLngRectangle.getUpperRight();
 
-    if (ur.getLng() < 0.0 && ll.getLng() > 0.0) {
-	longX2 = ll.getLng();
- 	longX = -180.0;	
+    double latY = upperRight.getLat();
+    double latX = lowerLeft.getLat();
+
+    double lngY = upperRight.getLng();
+    double lngX = lowerLeft.getLng();
+    double lngX2 = 0.0;
+
+    if (lngY < 0.0 && lngX > 0.0) {
+      lngX2 = lngX;
+      lngX = -180;
     }
-    if (ur.getLng() > 0.0 && ll.getLng() < 0.0) {
-	longX2 = ll.getLng();
- 	longX = 0.0;	
+    if (lngY > 0.0 && lngX < 0.0) {
+      lngX2 = lngX;
+      lngX = 0.0;
     }
-    
+
-    //System.err.println("getBoxShape:"+latY+"," + longY);
-    //System.err.println("getBoxShape:"+latX+"," + longX);
-    CartesianTierPlotter ctp = new CartesianTierPlotter(2, projector,tierPrefix);
-    int bestFit = ctp.bestFit(miles);
+    int bestFit = CartesianTierPlotter.bestFit(miles);
-    
+
-    ctp = new CartesianTierPlotter(bestFit, projector,tierPrefix);
-    Shape shape = new Shape(ctp.getTierFieldName());
+    CartesianTierPlotter tierPlotter = new CartesianTierPlotter(bestFit, PROJECTOR);
+    CartesianShape shape = new CartesianShape(bestFit);
+    addBoxes(shape, tierPlotter, latX, lngX, latY, lngY);
-    
+
-    // generate shape
-    // iterate from startX->endX
-    //     iterate from startY -> endY
-    //      shape.add(currentLat.currentLong);
-
-    shape = getShapeLoop(shape,ctp,latX,longX,latY,longY);
-    if (longX2 != 0.0) {
-	if (longX2 != 0.0) {
-		if (longX == 0.0) {
-			longX = longX2;
-			longY = 0.0;
-        		shape = getShapeLoop(shape,ctp,latX,longX,latY,longY);
+    if (lngX2 != 0.0) {
+      if (lngX == 0.0) {
+        lngX = lngX2;
+        lngY = 0.0;
+        addBoxes(shape, tierPlotter, latX, lngX, latY, lngY);
-		} else {
+      } else {
-			longX = longX2;
-			longY = -180.0;
-        		shape = getShapeLoop(shape,ctp,latY,longY,latX,longX);
+        lngX = lngX2;
+        lngY = -180.0;
+        addBoxes(shape, tierPlotter, latY, lngY, latX, lngX);
-		}
-	}
+      }
+    }
-        //System.err.println("getBoxShape2:"+latY+"," + longY);
-        //System.err.println("getBoxShape2:"+latX+"," + longX);
-    }
- 
-    return shape; 
-  } 
-  
+
+    return shape;
+  }
+
-  public Shape getShapeLoop(Shape shape, CartesianTierPlotter ctp, double latX, double longX, double latY, double longY)
-  {  
+  private void addBoxes(CartesianShape shape, CartesianTierPlotter tierPlotter, double latX, double lngX, double latY, double lngY) {
+    double beginAt = tierPlotter.getTierBoxId(latX, lngX);
+    double endAt = tierPlotter.getTierBoxId(latY, lngY);
- 
+
-    //System.err.println("getShapeLoop:"+latY+"," + longY);
-    //System.err.println("getShapeLoop:"+latX+"," + longX);
-    double beginAt = ctp.getTierBoxId(latX, longX);
-    double endAt = ctp.getTierBoxId(latY, longY);
+    double tierVert = tierPlotter.getTierVerticalPosDivider();
-    
+
-    double tierVert = ctp.getTierVerticalPosDivider();
-    //System.err.println(" | "+ beginAt+" | "+ endAt);
-    
-    double startX = beginAt - (beginAt %1);
+    double startX = beginAt - (beginAt % 1);
-    double startY = beginAt - startX ; //should give a whole number
-    
-    double endX = endAt - (endAt %1);
+    double endX = endAt - (endAt % 1);
-    double endY = endAt -endX; //should give a whole number
-    
+
-    int scale = (int)Math.log10(tierVert);
+    int scale = (int) Math.log10(tierVert);
-    endY = new BigDecimal(endY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
-    startY = new BigDecimal(startY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
-    double xInc = 1.0d / tierVert;
-    xInc = new BigDecimal(xInc).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
+    double startY = new BigDecimal(beginAt - startX).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
+    double endY = new BigDecimal(endAt - endX).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
-    
+
-    //System.err.println("go from startX:"+startX+" to:" + endX);
-    for (; startX <= endX; startX++){
+    double xInc = new BigDecimal(1.0D / tierVert).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
-      
+
+    for (; startX <= endX; startX++) {
       double itY = startY;
-      //System.err.println("go from startY:"+startY+" to:" + endY);
-      while (itY <= endY){
+      while (itY <= endY) {
-        //create a boxId
-        // startX.startY
-        double boxId = startX + itY ;
+        double boxId = startX + itY;
-        shape.addBox(boxId);
-        //System.err.println("----"+startX+" and "+itY);
-        //System.err.println("----"+boxId);
-        itY += xInc;
-        
-        // java keeps 0.0001 as 1.0E-1
-        // which ends up as 0.00011111
-        itY = new BigDecimal(itY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
+        shape.addBoxId(boxId);
+        // java keeps 0.0001 as 1.0E-1 which ends up as 0.00011111
+        itY = new BigDecimal(itY + xInc).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
       }
     }
-    return shape;
   }
-  
-  public Filter getBoundingArea(double latitude, double longitude, double miles) 
-  {
-    Shape shape = getBoxShape(latitude, longitude, miles);
-    return new CartesianShapeFilter(shape, shape.getTierId());
-  }
+}
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java	(revision 812248)
@@ -1,117 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.geometry.shape;
-
-
-/**
- * 2d line segment.
- *
- * <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 LineSegment {
-  public final Point2D A = new Point2D();
-  public final Point2D B = new Point2D();
-
-  public LineSegment() {
-    A.set(0, 0);
-    B.set(0, 0);
-  }
-
-  public LineSegment(Point2D p1, Point2D p2) {
-    A.set(p1);
-    B.set(p2);
-  }
-
-  /**
-   * Finds the distance of a specified point from the line segment and the
-   * closest point on the segment to the specified point.
-   * 
-   * @param P
-   *            Test point.
-   * @param closestPt
-   *            (Return) Closest point on the segment to c.
-   * 
-   * @return Returns the distance from P to the closest point on the segment.
-   */
-  public double distance(Point2D P, Point2D /* out */closestPt) {
-    if (closestPt == null)
-      closestPt = new Point2D();
-
-    // Construct vector v (AB) and w (AP)
-    Vector2D v = new Vector2D(A, B);
-    Vector2D w = new Vector2D(A, P);
-
-    // Numerator of the component of w onto v. If <= 0 then A
-    // is the closest point. By separating into the numerator
-    // and denominator of the component we avoid a division unless
-    // it is necessary.
-    double n = w.dot(v);
-    if (n <= 0.0f) {
-      closestPt.set(A);
-      return w.norm();
-    }
-
-    // Get the denominator of the component. If the component >= 1
-    // (d <= n) then point B is the closest point
-    double d = v.dot(v);
-    if (d <= n) {
-      closestPt.set(B);
-      return new Vector2D(B, P).norm();
-    }
-
-    // Closest point is along the segment. The point is the projection of
-    // w onto v.
-    closestPt.set(v.mult(n / d));
-    closestPt.add(A);
-    return new Vector2D(closestPt, P).norm();
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = 1;
-    result = prime * result + ((A == null) ? 0 : A.hashCode());
-    result = prime * result + ((B == null) ? 0 : B.hashCode());
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (obj == null)
-      return false;
-    if (getClass() != obj.getClass())
-      return false;
-    LineSegment other = (LineSegment) obj;
-    if (A == null) {
-      if (other.A != null)
-        return false;
-    } else if (!A.equals(other.A))
-      return false;
-    if (B == null) {
-      if (other.B != null)
-        return false;
-    } else if (!B.equals(other.B))
-      return false;
-    return true;
-  }
-  
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/projection/Projector.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/projection/Projector.java	Wed Dec 09 16:23:55 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/projection/Projector.java	Wed Dec 09 16:23:55 CET 2009
@@ -0,0 +1,43 @@
+/**
+ * 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.tier.projection;
+
+/**
+ * Projectors project a longitude/latitude pair to an x/y coordinate pair using standard projection algorithms such as
+ * sinusoidal projection.
+ *
+ * <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 interface Projector {
+
+  /**
+   * Projects the given latitude and longitude to an x/y coordinate pair
+   *
+   * @param latitude Latitude to project
+   * @param longitude Longitude to project
+   * @return x/y coordinate pair created by projecting the latitude and longitude
+   */
+  public double[] coords(double latitude, double longitude);
+
+  /**
+   * ???
+   *
+   * @return ???
+   */
+  public double[] range();
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Rectangle.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Rectangle.java	(revision 812248)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/Rectangle.java	Wed Dec 09 16:23:55 CET 2009
@@ -15,114 +15,43 @@
  * limitations under the License.
  */
 
-package org.apache.lucene.spatial.geometry.shape;
+package org.apache.lucene.spatial.geometry;
 
-
 /**
- * Rectangle shape.  
+ * Rectangle shape.
  *
- * <p><font color="red"><b>NOTE:</b> This API is still in
- * flux and might change in incompatible ways in the next
- * release.</font>
+ * <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 Rectangle implements Geometry2D {
-  private Point2D ptMin, ptMax;
+public class Rectangle {
-  
+
-  public Rectangle() {
-    ptMin=new Point2D(-1, 1);
-    ptMax=new Point2D(1, 1);
-  }
+  private Point ptMin;
+  private Point ptMax;
-  
+
-  public Rectangle(Point2D ptMin, Point2D ptMax) {
-    this.ptMin=new Point2D(ptMin);
-    this.ptMax=new Point2D(ptMax);
-  }
-  
   public Rectangle(double x1, double y1, double x2, double y2) {
-    set(x1, y1, x2, y2);
+    this.ptMin = new Point(Math.min(x1, x2), Math.min(y1, y2));
+    this.ptMax = new Point(Math.max(x1, x2), Math.max(y1, y2));
   }
 
-  @Override
-  public String toString() {
-    return "[" + ptMin + "," + ptMax + "]";
+  /**
+   * Determines whether the Rectangle contains the given point
+   *
+   * @param point Point to check if the rectangle contains it
+   * @return {@code true} if the Rectangle contains the point, {@code false} otherwise
+   */
+  public boolean contains(Point point) {
+    return point.getX() >= ptMin.getX() &&
+        point.getX() <= ptMax.getX() &&
+        point.getY() >= ptMin.getY() &&
+        point.getY() <= ptMax.getY();
   }
-  
+
-  private void set(double x1, double y1, double x2, double y2) {
-    this.ptMin=new Point2D(Math.min(x1, x2), Math.min(y1, y2));
-    this.ptMax=new Point2D(Math.max(x1, x2), Math.max(y1, y2));
-  }
+  // =============================================== Getters / Setters ===============================================
-  
+
-  public double area() {
-    return (ptMax.getX() - ptMin.getX()) * (ptMax.getY() - ptMin.getY());
-  }
-
-  public Point2D centroid() {
-    return new Point2D( (ptMin.getX() + ptMax.getX()) / 2,
-                  (ptMin.getY() + ptMax.getY()) / 2);
-  }
-
-  public boolean contains(Point2D p) {
-    return p.getX() >= ptMin.getX() && 
-      p.getX() <= ptMax.getX() &&
-      p.getY() >= ptMin.getY() &&
-      p.getY() <= ptMax.getY();
-  }
-
-  public void translate(Vector2D v) {
-    ptMin.add(v);
-    ptMax.add(v);
-  }
-
-  Point2D MinPt() {
-    return ptMin;
-  }
-
-  Point2D MaxPt() {
+  public Point getMaxPoint() {
     return ptMax;
   }
 
-  public IntersectCase intersect(Rectangle r) {
-    throw new UnsupportedOperationException();
-    // TODO
-  }
-
-  public Point2D getMaxPoint() {
-    return ptMax;
-  }
-
-  public Point2D getMinPoint() {
+  public Point getMinPoint() {
     return ptMin;
   }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = 1;
-    result = prime * result + ((ptMax == null) ? 0 : ptMax.hashCode());
-    result = prime * result + ((ptMin == null) ? 0 : ptMin.hashCode());
-    return result;
-  }
+}
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj)
-      return true;
-    if (obj == null)
-      return false;
-    if (getClass() != obj.getClass())
-      return false;
-    Rectangle other = (Rectangle) obj;
-    if (ptMax == null) {
-      if (other.ptMax != null)
-        return false;
-    } else if (!ptMax.equals(other.ptMax))
-      return false;
-    if (ptMin == null) {
-      if (other.ptMin != null)
-        return false;
-    } else if (!ptMin.equals(other.ptMin))
-      return false;
-    return true;
-  }
-
-}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianTierPlotter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianTierPlotter.java	Wed Dec 09 16:23:55 CET 2009
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianTierPlotter.java	Wed Dec 09 16:23:55 CET 2009
@@ -0,0 +1,138 @@
+/**
+ * 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.tier;
+
+import org.apache.lucene.spatial.tier.projection.Projector;
+
+/**
+ * CartesianTierPlotter provides functionality for plotting a point defined by a longitude and latitude, in a box thats
+ * part of a cartesian tier.
+ *
+ * <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 CartesianTierPlotter {
+  private static final double IDD = 180;
+  private static final double LOG_2 = Math.log(2);
+
+  private final int tierLevel;
+  private final int tierLength;
+  private final int tierVerticalPosDivider;
+  private final Projector projector;
+
+  public CartesianTierPlotter(int tierLevel, Projector projector) {
+    this.tierLevel = tierLevel;
+    this.projector = projector;
+
+    this.tierLength = (int) Math.pow(2, this.tierLevel);
+    this.tierVerticalPosDivider = calculateTierVerticalPosDivider();
+  }
+
+  /**
+   * Find the tier with the best fit for a bounding box. Best fit is defined as the ceiling of log2 (circumference of
+   * earth / distance) distance is defined as the smallest box fitting the corner between a radius and a bounding box.
+   * <p/>
+   * Distances less than a mile return 15, finer granularity is in accurate
+   *
+   * @param miles Distance in miles of the bounding box that the tier should be a best fit for
+   * @return Tier level with the best fit for the bounding box
+   */
+  public static int bestFit(double miles) {
+    //28,892 a rough circumference of the earth
+    // TODO - Change to SpatialConstants
+    int circ = 28892;
+
+    double r = miles / 2.0;
+
+    double corner = r - Math.sqrt(Math.pow(r, 2) / 2.0d);
+    double times = circ / corner;
+    int bestFit = (int) Math.ceil(log2(times)) + 1;
+
+    if (bestFit > 15) {
+      // 15 is the granularity of about 1 mile
+      // finer granularity isn't accurate with standard java math
+      return 15;
+    }
+    return bestFit;
+  }
+
+  /**
+   * TierBoxId is latitude box id + longitude box id where latitude box id, and longitude box id are transposded in to
+   * position coordinates.
+   *
+   * @param latitude Latitude of the point whose box id is to be returned
+   * @param longitude Longitude of the point whose box id is to be returned
+   * @return Id of the box where the point with the latitude and longitude, is located
+   */
+  public double getTierBoxId(double latitude, double longitude) {
+    double[] coords = projector.coords(latitude, longitude);
+    return getBoxId(coords[0]) + (getBoxId(coords[1]) / tierVerticalPosDivider);
+  }
+
+  // =============================================== Getters / Setters ===============================================
+
+  /**
+   * Calculates the nearest max power of 10 greater than the tierlen
+   * <p/>
+   * e.g tierId of 13 has tierLen 8192 nearest max power of 10 greater than tierLen would be 10,000
+   *
+   * @return Nearest max power of 10 greater than the tier len
+   */
+  private int calculateTierVerticalPosDivider() {
+    return (int) Math.pow(10, (int) Math.ceil(Math.log10(this.tierLength)));
+  }
+
+  /**
+   * Returns the nearest max power of 10 greater than the tier len
+   *
+   * @return Nearest max power of 10 greater than the tier len
+   */
+  public double getTierVerticalPosDivider() {
+    return tierVerticalPosDivider;
+  }
+
+  /**
+   * Returns the ID of the tier level plotting is occuring at
+   *
+   * @return ID of the tier level plotting is occuring at
+   */
+  public int getTierLevelId() {
+    return this.tierLevel;
+  }
+
+  // ================================================ Helper Methods =================================================
+
+  /**
+   * Returns the box id of the given coordinate
+   *
+   * @param coord Coordinate whose box id is to be computed
+   * @return Box id of the coordinate
+   */
+  private double getBoxId(double coord) {
+    return Math.floor(coord / (IDD / this.tierLength));
+  }
+
+  /**
+   * Computes log to base 2 of the given value
+   *
+   * @param value Value to compute the log of
+   * @return Log_2 of the value
+   */
+  public static double log2(double value) {
+    return Math.log(value) / LOG_2;
+  }
+}
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java	(revision 834847)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java	(revision 834847)
@@ -1,104 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.lucene.spatial.tier;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Provide a high level access point to distances
- * Used by DistanceSortSource and DistanceQuery
- *  
- * <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 DistanceHandler {
-
-  public enum Precision {EXACT, TWOFEET, TWENTYFEET, TWOHUNDREDFEET};
-  
-  private Map<Integer,Double> distances;
-  private Map<String, Double> distanceLookupCache;
-  private Precision precise;
-  
-  public DistanceHandler (Map<Integer,Double> distances, Map<String, Double> distanceLookupCache, Precision precise){
-    this.distances = distances;
-    this.distanceLookupCache = distanceLookupCache;
-    this.precise = precise; 
-  }
-  
-  
-  public static double getPrecision(double x, Precision thisPrecise){
-    
-    if(thisPrecise != null){
-      double dif = 0;
-      switch(thisPrecise) {
-        case EXACT: return x;
-        case TWOFEET:        dif = x % 0.0001; break;
-        case TWENTYFEET:     dif = x % 0.001;  break;
-        case TWOHUNDREDFEET: dif = x % 0.01; break;
-      }
-      return x - dif;
-    }
-    return x;
-  }
-  
-  public Precision getPrecision() {
-    return precise;
-  }
-  
-  public double getDistance(int docid, double centerLat, double centerLng, double lat, double lng){
-  
-    // check to see if we have distances
-    // if not calculate the distance
-    if(distances == null){
-      return DistanceUtils.getInstance().getDistanceMi(centerLat, centerLng, lat, lng);
-    }
-    
-    // check to see if the doc id has a cached distance
-    Double docd = distances.get( docid );
-    if (docd != null){
-      return docd.doubleValue();
-    }
-    
-    //check to see if we have a precision code
-    // and if another lat/long has been calculated at
-    // that rounded location
-    if (precise != null) {
-      double xLat = getPrecision(lat, precise);
-      double xLng = getPrecision(lng, precise);
-      
-      String k = Double.valueOf(xLat).toString() +","+ Double.valueOf(xLng).toString();
-    
-      Double d = (distanceLookupCache.get(k));
-      if (d != null){
-        return d.doubleValue();
-      }
-    }
-    
-    //all else fails calculate the distances    
-    return DistanceUtils.getInstance().getDistanceMi(centerLat, centerLng, lat, lng);
-  }
-  
-  
-  public static void main(String args[]){ 
-    DistanceHandler db = new DistanceHandler(new HashMap<Integer,Double>(), new HashMap<String,Double>(), Precision.TWOHUNDREDFEET);
-    System.out.println(DistanceHandler.getPrecision(-1234.123456789, db.getPrecision()));
-  }
-}
