You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@lucene.apache.org by si...@apache.org on 2010/01/05 23:03:49 UTC
svn commit: r896240 - in /lucene/java/trunk/contrib: ./
spatial/src/java/org/apache/lucene/spatial/geohash/
spatial/src/java/org/apache/lucene/spatial/geometry/
spatial/src/test/org/apache/lucene/spatial/geohash/
spatial/src/test/org/apache/lucene/spat...
Author: simonw
Date: Tue Jan 5 22:03:48 2010
New Revision: 896240
URL: http://svn.apache.org/viewvc?rev=896240&view=rev
Log:
LUCENE-2147: Improve Spatial Utility like classes
Added:
lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/
lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java (with props)
lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/
lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java (with props)
Modified:
lucene/java/trunk/contrib/CHANGES.txt
lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java
lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java
Modified: lucene/java/trunk/contrib/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/CHANGES.txt?rev=896240&r1=896239&r2=896240&view=diff
==============================================================================
--- lucene/java/trunk/contrib/CHANGES.txt (original)
+++ lucene/java/trunk/contrib/CHANGES.txt Tue Jan 5 22:03:48 2010
@@ -32,7 +32,11 @@
* LUCENE-2108: Add SpellChecker.close, to close the underlying
reader. (Eirik Bjørsnøs via Mike McCandless)
-
+
+ * LUCENE-2147: Spatial GeoHashUtils now always decode GeoHash strings
+ with full precision. GeoHash#decode_exactly(String) was merged into
+ GeoHash#decode(String). (Chris Male, Simon Willnauer)
+
* LUCENE-2165: Add a constructor to SnowballAnalyzer that takes a Set of
stopwords, and deprecate the String[] one. (Nick Burch via Robert Muir)
Modified: lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java?rev=896240&r1=896239&r2=896240&view=diff
==============================================================================
--- lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java (original)
+++ lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashUtils.java Tue Jan 5 22:03:48 2010
@@ -21,154 +21,119 @@
import java.util.Map;
/**
- * 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>
+ * Utilities for encoding and decoding geohashes. Based on
+ * http://en.wikipedia.org/wiki/Geohash.
*/
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 final static Map<Character, Integer> _decodemap = new HashMap<Character, Integer>();
- static {
- int sz = _base32.length;
- for (int i = 0; i < sz; i++ ){
- _decodemap.put(_base32[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]);
-
- }
-
- public static String encode(double latitude, double longitude){
- double[] lat_interval = {-90.0 , 90.0};
- double[] lon_interval = {-180.0, 180.0};
-
- StringBuilder geohash = new StringBuilder();
- boolean is_even = true;
- int bit = 0, ch = 0;
-
- while(geohash.length() < precision){
- double mid = 0.0;
- if(is_even){
- mid = (lon_interval[0] + lon_interval[1]) / 2;
- if (longitude > mid){
- ch |= bits[bit];
- lon_interval[0] = mid;
- } else {
- lon_interval[1] = mid;
- }
-
- } else {
- mid = (lat_interval[0] + lat_interval[1]) / 2;
- if(latitude > mid){
- ch |= bits[bit];
- lat_interval[0] = mid;
- } else {
- lat_interval[1] = mid;
- }
- }
-
- is_even = is_even ? false : true;
-
- if (bit < 4){
- bit ++;
- } else {
- geohash.append(_base32[ch]);
- bit =0;
- ch = 0;
- }
- }
-
- return geohash.toString();
- }
-
- 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 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;
-
- lat = getPrecision(lat, lat_precision);
- lon = getPrecision(lon, lon_precision);
-
- 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;
- if ((cd & mask) != 0){
- lon_interval[0] = (lon_interval[0]+lon_interval[1])/2;
- } else {
- lon_interval[1] = (lon_interval[0]+lon_interval[1])/2;
- }
-
- } else {
- lat_err /=2;
-
- if ( (cd & mask) != 0){
- lat_interval[0] = (lat_interval[0]+lat_interval[1])/2;
- } else {
- lat_interval[1] = (lat_interval[0]+lat_interval[1])/2;
- }
- }
- is_even = is_even ? false : true;
- }
-
- }
- latitude = (lat_interval[0] + lat_interval[1]) / 2;
- longitude = (lon_interval[0] + lon_interval[1]) / 2;
+ 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'};
- return new double []{latitude, longitude, lat_err, lon_err};
- }
-
- static double getPrecision(double x, double precision) {
- double base = Math.pow(10,- precision);
- double diff = x % base;
- return x - diff;
+ 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 {
+ for (int i = 0; i < BASE_32.length; i++) {
+ DECODE_MAP.put(Character.valueOf(BASE_32[i]), Integer.valueOf(i));
+ }
+ }
+
+ 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) {
+ double[] latInterval = {-90.0, 90.0};
+ double[] lngInterval = {-180.0, 180.0};
+
+ final StringBuilder geohash = new StringBuilder();
+ boolean isEven = true;
+
+ int bit = 0;
+ int ch = 0;
+
+ while (geohash.length() < PRECISION) {
+ double mid = 0.0;
+ if (isEven) {
+ mid = (lngInterval[0] + lngInterval[1]) / 2D;
+ if (longitude > mid) {
+ ch |= BITS[bit];
+ lngInterval[0] = mid;
+ } else {
+ lngInterval[1] = mid;
+ }
+ } else {
+ mid = (latInterval[0] + latInterval[1]) / 2D;
+ if (latitude > mid) {
+ ch |= BITS[bit];
+ latInterval[0] = mid;
+ } else {
+ latInterval[1] = mid;
+ }
+ }
+
+ isEven = !isEven;
+
+ if (bit < 4) {
+ bit++;
+ } else {
+ geohash.append(BASE_32[ch]);
+ bit = 0;
+ 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) {
+ final double[] latInterval = {-90.0, 90.0};
+ final double[] lngInterval = {-180.0, 180.0};
+
+ boolean isEven = true;
+
+ double latitude;
+ double longitude;
+ for (int i = 0; i < geohash.length(); i++) {
+ final int cd = DECODE_MAP.get(Character.valueOf(
+ geohash.charAt(i))).intValue();
+
+ for (int mask : BITS) {
+ if (isEven) {
+ if ((cd & mask) != 0) {
+ lngInterval[0] = (lngInterval[0] + lngInterval[1]) / 2D;
+ } else {
+ lngInterval[1] = (lngInterval[0] + lngInterval[1]) / 2D;
+ }
+ } else {
+ if ((cd & mask) != 0) {
+ latInterval[0] = (latInterval[0] + latInterval[1]) / 2D;
+ } else {
+ latInterval[1] = (latInterval[0] + latInterval[1]) / 2D;
+ }
+ }
+ isEven = !isEven;
+ }
+
+ }
+ latitude = (latInterval[0] + latInterval[1]) / 2D;
+ longitude = (lngInterval[0] + lngInterval[1]) / 2D;
+
+ return new double[] {latitude, longitude};
}
-}
+}
\ No newline at end of file
Modified: lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java?rev=896240&r1=896239&r2=896240&view=diff
==============================================================================
--- lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java (original)
+++ lucene/java/trunk/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java Tue Jan 5 22:03:48 2010
@@ -18,11 +18,93 @@
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
*/
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;
+ }
+
+ /**
+ * 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;
+ }
}
+
Added: lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java?rev=896240&view=auto
==============================================================================
--- lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java (added)
+++ lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java Tue Jan 5 22:03:48 2010
@@ -0,0 +1,89 @@
+/**
+ * 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 static junit.framework.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link GeoHashUtils}
+ */
+public class TestGeoHashUtils {
+
+ /**
+ * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96",
+ * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8"
+ */
+ @Test
+ 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
+ */
+ @Test
+ 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
+ */
+ @Test
+ 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
+ */
+ @Test
+ 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);
+ }
+}
Propchange: lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geohash/TestGeoHashUtils.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Added: lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java?rev=896240&view=auto
==============================================================================
--- lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java (added)
+++ lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java Tue Jan 5 22:03:48 2010
@@ -0,0 +1,46 @@
+package org.apache.lucene.spatial.geometry;
+
+import static junit.framework.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link org.apache.lucene.spatial.geometry.DistanceUnits}
+ */
+public class TestDistanceUnits {
+
+ /**
+ * Pass condition: When finding the DistanceUnit for "km", KILOMETRES is found. When finding the DistanceUnit for
+ * "miles", MILES is found.
+ */
+ @Test
+ 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.
+ */
+ @Test
+ 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
+ */
+ @Test
+ 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);
+ }
+}
Propchange: lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/java/trunk/contrib/spatial/src/test/org/apache/lucene/spatial/geometry/TestDistanceUnits.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL