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	Mon Dec 21 11:12:21 CET 2009
@@ -18,11 +18,96 @@
 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>
+ * 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 DistanceUnits {
-  MILES,
-  KILOMETERS;
+
+  MILES("miles", 3959, 24902),
+  KILOMETERS("km", 6371, 40076);
+
+  private static final double MILES_KILOMETRES_RATIO = 1.609344;
+
+  private final String unit;
+  
+  private final double earthCircumference;
+  
+  private final double earthRadius;
+
+  /**
+   * Creates a new DistanceUnit that represents the given unit
+   *
+   * @param unit Distance unit in String form
+   * @param earthRadius Radius of the Earth in the specific distance unit
+   * @param earthCircumfence Circumference of the Earth in the specific distance unit
+   */
+  DistanceUnits(String unit, double earthRadius, double earthCircumfence) {
+    this.unit = unit;
+    this.earthCircumference = earthCircumfence;
+    this.earthRadius = earthRadius;
-}
+  }
+
+  /**
+   * 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 DistanceUnits 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 in given DistanceUnit, to a distance in the unit represented by {@code this} 
+   *
+   * @param distance Distance to convert
+   * @param from Unit to convert the distance from
+   * @return Given distance converted to the distance in the given unit
+   */
+  public double convert(double distance, DistanceUnits from) {
+    if (from == this) {
+      return distance;
+    }
+    return (this == 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;
+  }
+  
+  /**
+   * Returns the <a href="http://en.wikipedia.org/wiki/Earth_radius">average earth radius</a>
+   *
+   * @return the average earth radius
+   */
+  public double earthRadius() {
+    return earthRadius;
+  }
+  
+  /**
+   * Returns the <a href="http://www.lyberty.com/encyc/articles/earth.html">circumference of the Earth</a>
+   * 
+   * @return  the circumference of the Earth
+   */
+  public double earthCircumference() {
+    return earthCircumference;
+  }
+}
+
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/geohash/GeoHashUtils.java	Fri Dec 18 12:00:24 CET 2009
@@ -21,154 +21,120 @@
 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 final 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 final int PRECISION = 12;
+  private static final 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(Character.valueOf(BASE_32[i]), Integer.valueOf(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();
-		boolean is_even = true;
-		int bit = 0, ch = 0;
+    final StringBuilder geohash = new StringBuilder();
+    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];
+    final double[] latInterval = {-90.0, 90.0};
+    final 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++) {
+      final int cd = DECODE_MAP.get(Character.valueOf(
+          geohash.charAt(i))).intValue();
-		
+
-		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]) / 2D;
+    longitude = (lngInterval[0] + lngInterval[1]) / 2D;
 
-		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;
-	}
+}
\ No newline at end of file
-}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java	Fri Dec 18 12:00:24 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java	Fri Dec 18 12:00:24 CET 2009
@@ -0,0 +1,83 @@
+/**
+ * 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 junit.framework.TestCase;
+
+/**
+ * Tests for {@link 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 testDecodePreciseLongitudeLatitude() {
+    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 testDecodeImpreciseLongitudeLatitude() {
+    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);
+  }
+  
+  /*
+   * see https://issues.apache.org/jira/browse/LUCENE-1815 for details
+   */
+  public void testDecodeEncode() {
+    String geoHash = "u173zq37x014";
+    assertEquals(geoHash, GeoHashUtils.encode(52.3738007, 4.8909347));
+    double[] decode = GeoHashUtils.decode(geoHash);
+    assertEquals(52.37380061d, decode[0], 0.000001d);
+    assertEquals(4.8909343d, decode[1], 0.000001d);
+    
+    assertEquals(geoHash, GeoHashUtils.encode(decode[0], decode[1]));
+    
+    geoHash = "u173";
+    decode = GeoHashUtils.decode("u173");
+    geoHash = GeoHashUtils.encode(decode[0], decode[1]);
+    assertEquals(decode[0], GeoHashUtils.decode(geoHash)[0], 0.000001d);
+    assertEquals(decode[1], GeoHashUtils.decode(geoHash)[1], 0.000001d);
+  }
+}
Index: contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java	Fri Dec 18 12:00:24 CET 2009
+++ contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java	Fri Dec 18 12:00:24 CET 2009
@@ -0,0 +1,41 @@
+package org.apache.lucene.spatial.geometry;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.geometry.DistanceUnits}
+ */
+public class TestDistanceUnits 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(DistanceUnits.KILOMETERS, DistanceUnits.findDistanceUnit("km"));
+    assertEquals(DistanceUnits.MILES, DistanceUnits.findDistanceUnit("miles"));
+  }
+
+  /**
+   * Pass condition: Searching for the DistanceUnit of an unknown unit "mls" should throw an IllegalArgumentException.
+   */
+  public void testFindDistanceUnit_unknownUnit() {
+    try {
+      DistanceUnits.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, DistanceUnits.MILES.convert(10.5, DistanceUnits.MILES), 0D);
+    assertEquals(10.5, DistanceUnits.KILOMETERS.convert(10.5, DistanceUnits.KILOMETERS), 0D);
+    assertEquals(10.5 * 1.609344, DistanceUnits.KILOMETERS.convert(10.5, DistanceUnits.MILES), 0D);
+    assertEquals(10.5 / 1.609344, DistanceUnits.MILES.convert(10.5, DistanceUnits.KILOMETERS), 0D);
+  }
+}
