这个算法介绍就不说了。主要的功能就是把2维的数据近似转化为1维数据,方便数据的检索。主要应用就是地图坐标转1维标示了。
这个算法我稍微改了下,所以和网上的其他版本不太一样,主要修改一些bug,和添加一些其他功能接口。
import java.util.BitSet;
import java.util.HashMap;
public class Geohash {
public static final Geohash GEOHASH = new Geohash();
/**
* 上为1 下为0 左为0 右为1 先横后竖分割, 对应编码与位置关系数组 下标=> char
* p r x z
* n q w y
* j m t v
* h k s u
* 5 7 e g
* 4 6 d f
* 1 3 9 c
* 0 2 8 b
*
*/
private final static char[][] DTCRawFirst = {{'p', 'r', 'x', 'z'},
{'n', 'q', 'w', 'y'},
{'j', 'm', 't', 'v'},
{'h', 'k', 's', 'u'},
{'5', '7', 'e', 'g'},
{'4', '6', 'd', 'f'},
{'1', '3', '9', 'c'},
{'0', '2', '8', 'b'}};
/**
* 对应编码与位置关系map char => 下标
*/
private final static HashMap lookupRawFirst = new HashMap();
/**
* 上为1 下为0 左为0 右为1 先竖后横 下标 => char
* b c f g u v y z
* 8 9 d e s t w x
* 2 3 6 7 k m q r
* 0 1 4 5 h j n p
*/
final static char[][] DTCColumnFirst = {{'b', 'c', 'f', 'g', 'u', 'v', 'y', 'z'},
{'8', '9', 'd', 'e', 's', 't', 'w', 'x'},
{'2', '3', '6', '7', 'k', 'm', 'q', 'r'},
{'0', '1', '4', '5', 'h', 'j', 'n', 'p'}};
/**
* 对应编码与位置关系map char => 下标
*/
final static HashMap lookupColumnFirst = new HashMap();
/**
* level用于求Geohash的精度, 下标加1为精度, 精度指Geohash编码后的字符位数。 level中对应的数值表示 在此精度下, 一块区域纬度的跨度。
*/
final static double[] level = {45, 5.625, 1.40625, 0.17578125, 0.04394531,
0.00549316, 0.00137329, 0.00017166, 0.00004292, 0.00000536};
/**
* 默认纬度和经度都是解析6*5位
*/
private static int numbits = 6 * 5;
/**
* base32 int => char
*/
final static char[] digits = { '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' };
/**
* base32 char => int
*/
final static HashMap lookup = new HashMap();
static {
int i = 0;
for (char c : digits)
lookup.put(c, i++);
int j = 0;
i = 0;
for(; i < 8; i++)
for(j = 0; j < 4; j++)
lookupRawFirst.put(DTCRawFirst[i][j], new Integer[] {i, j});
for(i = 0; i < 4; i++)
for(j = 0; j < 8; j++)
lookupColumnFirst.put(DTCColumnFirst[i][j], new Integer[] {i, j});
}
/**
* 将geohash code 解析为坐标值
* @param geohash code
* @return 坐标值
*/
public double[] decode(String geohash) {
StringBuilder buffer = new StringBuilder();
for (char c : geohash.toCharArray()) {
int i = lookup.get(c) + 32;
System.out.println("num : "+i + " char : "+c+" digits : "+Integer.toString(i, 2).substring(1));
buffer.append(Integer.toString(i, 2).substring(1));
}
BitSet lonset = new BitSet();
BitSet latset = new BitSet();
// even bits
int j = 0;
for (int i = 0; i < numbits * 2; i += 2) {
boolean isSet = false;
if (i < buffer.length())
isSet = buffer.charAt(i) == '1';
lonset.set(j++, isSet);
}
// odd bits
j = 0;
for (int i = 1; i < numbits * 2; i += 2) {
boolean isSet = false;
if (i < buffer.length())
isSet = buffer.charAt(i) == '1';
latset.set(j++, isSet);
}
System.out.println("buffer length : "+buffer.length()+" lat bitset size : "+latset.size() + " lng bitset size : "+lonset.size());
double lon = decode(lonset, -180, 180);
double lat = decode(latset, -90, 90);
return new double[] { lat, lon };
}
/**
* 将geohash code 转化为坐标区域
* @param geohash code
* @param precision 精度
* @return {西北点纬度, 西北点经度, 东南点纬度, 东南点经度}
*/
public double[] decode(String geohash, int precision) {
StringBuilder buffer = new StringBuilder();
for(int i = 0; i < precision; i++) {
char c = geohash.charAt(i);
int j = lookup.get(c)+32;
buffer.append(Integer.toString(j, 2).substring(1));
}
BitSet lonset = new BitSet();
BitSet latset = new BitSet();
// even bits
int j = 0;
for (int i = 0; i < precision * 5; i += 2) {
boolean isSet = false;
if (i < buffer.length())
isSet = buffer.charAt(i) == '1';
lonset.set(j++, isSet);
}
// odd bits
j = 0;
for (int i = 1; i < precision * 5; i += 2) {
boolean isSet = false;
if (i < buffer.length())
isSet = buffer.charAt(i) == '1';
latset.set(j++, isSet);
}
double lon[] = decode(lonset, -180, 180, precision, false);
double lat[] = decode(latset, -90, 90, precision, true);
return new double[] { lat[1], lon[0], lat[0], lon[1] };
}
/**
* 根据二进制的编码 解析出纬度或者经度的范围
* @param bs 二进制编码
* @param floor 初始下限
* @param ceiling 初始上限
* @param precision 精度
* @param isLat true为解析纬度 false为解析经度
* @return {范围下限, 范围上限}
*/
private double[] decode(BitSet bs, double floor, double ceiling, int precision, boolean isLat) {
double mid = 0;
int numbits = (isLat ? precision * 5/2 : (precision*5+1)/2);
for (int i = 0; i < numbits; i++) {
mid = (floor + ceiling) / 2;
if (bs.get(i))
floor = mid;
else
ceiling = mid;
}
return new double[] { floor, ceiling };
}
/**
* 根据二进制的编码 解析出纬度或者经度的值
* @param bs 二进制编码
* @param floor 初始下限
* @param ceiling 初始上限
* @return 纬度或者经度
*/
private double decode(BitSet bs, double floor, double ceiling) {
double mid = 0;
for (int i = 0; i < numbits; i++) {
mid = (floor + ceiling) / 2;
if (bs.get(i))
floor = mid;
else
ceiling = mid;
}
return mid;
}
/**
* 将坐标 转化为 geohash code
* @param lat 纬度
* @param lon 经度
* @return geohash code
*/
public String encode(double lat, double lon) {
BitSet latbits = getBits(lat, -90, 90);
BitSet lonbits = getBits(lon, -180, 180);
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < numbits; i++) {
buffer.append((lonbits.get(i)) ? '1' : '0');
buffer.append((latbits.get(i)) ? '1' : '0');
}
return base32(Long.parseLong(buffer.toString(), 2));
}
/**
* 将坐标 转化为 某一精度的geohash code
* @param lat 纬度
* @param lon 经度
* @param precision 精度
* @return 精度为precision的geohash code
*/
public String encode(double lat, double lon, int precision) {
BitSet latbits = getBits(lat, -90, 90, precision, true);
BitSet lngbits = getBits(lon, -180, 180, precision, false);
StringBuilder buffer = new StringBuilder();
for(int i = 0; i < 5*precision; i++) {
if(i%2 == 0) {
buffer.append((lngbits.get(i/2)) ? '1' : '0');
}else {
buffer.append((latbits.get((i - 1)/2)) ? '1' : '0');
}
}
return base32(Long.parseLong(buffer.toString(), 2), precision);
}
/**
* 获取纬度或者经度的二进制编码
* @param lat 纬度或者经度
* @param floor 初始下限
* @param ceiling 初始上限
* @return 二进制编码
*/
private BitSet getBits(double lat, double floor, double ceiling) {
BitSet buffer = new BitSet(numbits);
for (int i = 0; i < numbits; i++) {
double mid = (floor + ceiling) / 2;
if (lat >= mid) {
buffer.set(i);
floor = mid;
} else {
ceiling = mid;
}
}
return buffer;
}
/**
* 获取纬度或者经度在某一精度下的二进制编码
* @param lat 纬度或者经度
* @param floor 初始下限
* @param ceiling 初始上限
* @param precision 精度
* @param isLat true为纬度, false为经度
* @return 纬度或者经度的二进制编码
*/
private BitSet getBits(double lat, double floor, double ceiling, int precision, boolean isLat) {
int numbits = isLat ? 5*precision/2 : (5*precision+1)/2;
BitSet buffer = new BitSet(numbits);
for(int i = 0; i < numbits; i++) {
double mid = (floor + ceiling) / 2;
if(lat >= mid) {
buffer.set(i);
floor = mid;
}else {
ceiling = mid;
}
}
return buffer;
}
/**
* 将二进制编码进行base32编码, 获得最终的geohash code
* @param i 二进制编码
* @return geohash code
*/
public static String base32(long i) {
char[] buf = new char[65];
int charPos = 64;
boolean negative = (i < 0);
if (!negative)
i = -i;
int j = numbits/ 5 *2;
while (j-- > 1) {
buf[charPos--] = digits[(int) (-(i % 32))];
i /= 32;
}
buf[charPos] = digits[(int) (-i)];
if (negative)
buf[--charPos] = '-';
return new String(buf, charPos, (65 - charPos));
}
/**
* 将二进制编码转化为某一精度的geohash code
* @param i 二进制编码, 注意二进制位数和精度是一致的
* @param precision 精度
* @return geohash code
*/
public static String base32(long i, int precision) {
char[] buf = new char[65];
int charPos = 64;
boolean negative = (i < 0);
if (!negative)
i = -i;
int j = precision;
while (j-- > 1) {
buf[charPos--] = digits[(int) (-(i % 32))];
i /= 32;
}
buf[charPos] = digits[(int) (-i)];
if (negative)
buf[--charPos] = '-';
return new String(buf, charPos, (65 - charPos));
}
/**
* 计算在某一精度下 两个区域的行列差
* @param startIndex 开始区域索引
* @param endIndex 结束区域索引
* @param precision 精度
* @return {行差, 列差}
*/
public static int[] distance(final String startIndex, final String endIndex, final int precision) {
int raw = 0;
int column = 0;
char start, end;
for(int i = 0; i < precision; i++) {
start = startIndex.charAt(i);
end = endIndex.charAt(i);
if(i%2 == 0) {
Integer[] startPointer = lookupColumnFirst.get(start);
Integer[] endPointer = lookupColumnFirst.get(end);
raw = raw * 4 + endPointer[0] - startPointer[0];
column = column * 8 + endPointer[1] - startPointer[1];
}else {
Integer[] startPointer = lookupRawFirst.get(start);
Integer[] endPointer = lookupRawFirst.get(end);
raw = raw * 8 + endPointer[0] - startPointer[0];
column = column * 4 + endPointer[1] - startPointer[1];
}
}
return new int[]{raw, column};
}
public static String nextColumn(final String index, final int precision) {
boolean up = false;
char c;
char[] result = index.toCharArray();
int column;
for(int i = precision - 1; i >= 0; i--) {
c = index.charAt(i);
if(i%2 == 0) {
Integer[] pointer = lookupColumnFirst.get(c);
if(pointer[1] == 7){
column = 0;
up = true;
}else {
column = pointer[1] + 1;
up = false;
}
result[i] = DTCColumnFirst[pointer[0]][column];
}else {
Integer[] pointer = lookupRawFirst.get(c);
if(pointer[1] == 3) {
column = 0;
up = true;
}else {
column = pointer[1] + 1;
up = false;
}
result[i] = DTCRawFirst[pointer[0]][column];
}
if(!up)
break;
}
return new String(result);
}
public static String nextRaw(final String index, final int precision) {
boolean up = false;
char c;
char[] result = index.toCharArray();
int raw;
for(int i = precision - 1; i >= 0; i--) {
c = index.charAt(i);
if(i%2 == 0) {
Integer[] pointer = lookupColumnFirst.get(c);
if(pointer[0] == 3){
raw = 0;
up = true;
}else {
raw = pointer[0] + 1;
up = false;
}
result[i] = DTCColumnFirst[raw][pointer[1]];
}else {
Integer[] pointer = lookupRawFirst.get(c);
if(pointer[0] == 7) {
raw = 0;
up = true;
}else {
raw = pointer[0] + 1;
up = false;
}
result[i] = DTCRawFirst[raw][pointer[1]];
}
if(!up)
break;
}
return new String(result);
}
public static String[] aroundBlocks(final String index, final int precision) {
int up[][] = new int[][]{{3, 1, 3}
,{2, 0, 2}
,{3, 1, 3}};
char[][][] result = new char[3][3][];
char[] origin = index.substring(0, precision).toCharArray();
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
result[i][j] = origin.clone();
}
}
char c;
int raw, column;
boolean goon = false;
for(int i = precision - 1; i >= 0; i--) {
goon = false;
c = origin[i];
if(i%2 == 0) {
Integer[] pointer = lookupColumnFirst.get(c);
for(int m = 0; m < 3; m++) {
for(int n = 0; n < 3; n++) {
if(up[m][n] == 3) { //行列都需要改变
raw = pointer[0] + m -1;
column = pointer[1] + n -1;
}else if(up[m][n] == 2) { // 列需要改变
raw = pointer[0];
column = pointer[1] + n -1;
}else if(up[m][n] == 1) { // 行需要改变
raw = pointer[0] + m -1;
column = pointer[1];
}else {
up[m][n] = 0;
continue;
}
up[m][n] = 0;
if(raw == -1) { // 向上跨行
raw = 3;
up[m][n] += 1;
goon = true;
}else if(raw == 4) { // 向下跨行
raw = 0;
up[m][n] += 1;
goon = true;
}
if(column == -1) { //向左跨列
column = 7;
up[m][n] += 2;
goon = true;
}else if(column == 8) { // 向右跨列
column = 0;
up[m][n] += 2;
goon = true;
}
result[m][n][i] = DTCColumnFirst[raw][column];
}
}
}else {
Integer[] pointer = lookupRawFirst.get(c);
for(int m = 0; m < 3; m++) {
for(int n = 0; n < 3; n++) {
if(up[m][n] == 3) { //行列都需要改变
raw = pointer[0] + m -1;
column = pointer[1] + n -1;
}else if(up[m][n] == 2) { // 列需要改变
raw = pointer[0];
column = pointer[1] + n -1;
}else if(up[m][n] == 1) { // 行需要改变
raw = pointer[0] + m -1;
column = pointer[1];
}else {
up[m][n] = 0;
continue;
}
up[m][n] = 0;
if(raw == -1) { // 向上跨行
raw = 7;
up[m][n] += 1;
goon = true;
}else if(raw == 8) { // 向下跨行
raw = 0;
up[m][n] += 1;
goon = true;
}
if(column == -1) { //向左跨列
column = 3;
up[m][n] += 2;
goon = true;
}else if(column == 4) { // 向右跨列
column = 0;
up[m][n] += 2;
goon = true;
}
result[m][n][i] = DTCRawFirst[raw][column];
}
}
}
if(!goon)
break;
}
String ret[] = new String[9];
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
ret[i*3+j] = new String(result[i][j]);
}
}
return ret;
}
/**
* 获取字符在base32编码中对应的数字
* @param c base32中的字符
* @return 对应的数字
*/
public static int tellIndex(char c) {
return lookup.get(c);
}
}
博客探讨了Geohash算法如何将二维地理坐标转换为一维标识,以简化数据检索。博主分享了自己对算法的改进,修复了一些已知bug,并增加了额外的功能接口。
660

被折叠的 条评论
为什么被折叠?



