/**
* 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.util.HashMap;
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>
*/
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;
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;
}
}