Geohash算法

博客探讨了Geohash算法如何将二维地理坐标转换为一维标识,以简化数据检索。博主分享了自己对算法的改进,修复了一些已知bug,并增加了额外的功能接口。

这个算法介绍就不说了。主要的功能就是把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);
	}
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值