学习geohash算法

           基于地址进行数据的检索,这个貌似有点难度,如果是小的应用的话,可以根据经纬度信息来直接进行查询或者通过数据库本身的空间数据检索方案,但是如果数据量以及访问请求变大时,这中方案就显然不是很合适,往往会使请求变的很慢。

          经过一系列的沟通下来,可以通过geohash的方案来解决这个问题。

基本流程可以是这样:

      (1)原始详细地址数据--->经纬度数值--->geohash字符串编码--->数据冗余保存,主键换为geohash,然后原始数据后置保存

      (2)请求接口,参数为详细地址,详细地址进行转换成geohash,然后基于geohash编码来进行搜索和排序,返回结果

        详细地址转换为经纬度这个可以直接调取成熟的geocoding服务来进行解决,地址规范的情况下,定位到街道应该不会很大,虽然有时候有一定的偏差,但是民用的话基本可以接受呵呵。

         所以目前的流程的话卡在了geohash算法这里,所以写这篇文章详细的介绍一下。

geohash的最简单解释:将一个经纬度信息,转换成一个可排序、可比较的字符串编码。

         将经纬度的信息,按照(-90,90)(-180,180)来转换成平面坐标系。

         借用一篇文章中的例子来说明一下编码生成的过程:

         首先将纬度范围(-90, 90)平分成两个区间(-90, 0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。

         由于39.92324属于(0, 90),所以取编码为1。

         然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。

         以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。

                               

           经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。

                             

           接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。

           最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。

                               

           解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。

           不过由于geohash表示的是区间,编码越长越精确,但不可能解码出完全一致的地址。

           引用阿里云以为技术专家的博客上的讨论:

              

          常见的一些应用场景

          A、如果想查询附近的点?如何操作

                 查出改点的gehash值,然后到数据库里面进行前缀匹配就可以了。

          B、如果想查询附近点,特定范围内,例如一个点周围500米的点,如何搞?

                可以查询结果,在结果中进行赛选,将geohash进行解码为经纬度,然后进行比较

         1、在纬度相等的情况下:

              经度每隔0.00001度,距离相差约1米

              经度每隔0.0001度,距离相差约10米

              经度每隔0.001度,距离相差约100米

              经度每隔0.01度,距离相差约1000米

              经度每隔0.1度,距离相差约10000米

          2、在经度相等的情况下:

              纬度每隔0.00001度,距离相差约1.1米

              纬度每隔0.0001度,距离相差约11米

              纬度每隔0.001度,距离相差约111米

              纬度每隔0.01度,距离相差约1113米

              纬度每隔0.1度,距离相差约11132米

        代码直接贴出来,感兴趣的直接运行一下吧呵呵。

import java.text.DecimalFormat;
import java.util.BitSet;
import java.util.HashMap;

public class Geohash {

	private static int numbits = 6 * 5;
	private static String data = "y8dcb88bgcqs#KP2#wx4g0ebcgcnw#KP2#wx4g0ec9er26#KP2#wx4g0ec9g30q";
	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' };

	final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
	static {
		int i = 0;
		for (char c : digits)
			lookup.put(c, i++);
	}

	public static void main(String[] args) throws Exception {
		double lon = 116.39036, lat = 39.92324;
		System.out.println(new Geohash().encode(39.92325, 116.39136));
		System.out.println(getFinalResult(data, 100, lon, lat));
	}

	public static double[] decode(String geohash) {
		StringBuilder buffer = new StringBuilder();
		for (char c : geohash.toCharArray()) {

			int i = lookup.get(c) + 32;
			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);
		}

		double lon = decode(lonset, -180, 180);
		double lat = decode(latset, -90, 90);

		return new double[] { lon, lat };
	}

	private static double decode(BitSet bs, double floor, double ceiling) {
		double mid = 0;
		for (int i = 0; i < bs.length(); i++) {
			mid = (floor + ceiling) / 2;
			if (bs.get(i))
				floor = mid;
			else
				ceiling = mid;
		}
		return mid;
	}

	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));
	}

	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;
	}

	public static String base32(long i) {
		char[] buf = new char[65];
		int charPos = 64;
		boolean negative = (i < 0);
		if (!negative)
			i = -i;
		while (i <= -32) {
			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 myData sql中like前4位(wq32%)
	 * @param radius 附近距离相当于一个圆的半径
	 * @param longitude 经度
	 * @param latitude 纬度
	 * @return
	 */
	public static String getFinalResult(String myData, int radius,
			double longitude, double latitude) {
		String finalResult = "";
		try {
			if (myData != null && !"".equals(myData)) {
				// 实际经度半径
				double lonRadius = radius % 1000 == 0 ? (0.01 * radius / 1000)
						: (radius % 100 == 0 ? (0.001 * radius / 100)
								: (radius % 10 == 0 ? (0.0001 * radius / 10)
										: (0.00001 * radius)));
				// 实际纬度半径
				double latRadius = radius % 1000 == 0 ? (0.01 * radius * radius / (1113 * 1000))
						: (radius % 100 == 0 ? (0.001 * radius * radius / (100 * 111))
								: (radius % 10 == 0 ? (0.0001 * radius * radius / (10 * 11))
										: (0.00001 * radius * radius / 1.1)));
				String[] dataSplit = myData.split("#KP2#");
				for (int i = 0; i < dataSplit.length; i++) {
					String myTemp = dataSplit[i];
					//当前的纬度
					double currentLat = Geohash.decode(myTemp)[1];
					//当前的经度
					double currentLon = Geohash.decode(myTemp)[0];
					//当前的纬度和圆中心的纬度差
					double y = getFiveDecimal(Math.abs(latitude - currentLat)) * radius / latRadius;
					//当前的经度和圆中心的经度差
					double x = getFiveDecimal(Math.abs(longitude - currentLon)) * radius / lonRadius;
					//判断当前点是否在圆内
					if ((x * x + y * y) <= (radius * radius)) {
						finalResult += myTemp + "#KP2#";
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			finalResult = "";
		}
		if (finalResult != null && !"".equals(finalResult)) {
			finalResult = finalResult.substring(0, finalResult.length() - 5);
		}
		return finalResult;
	}

	// 获取最多保留5位小数点
	public static double getFiveDecimal(double d) {
		DecimalFormat df = new DecimalFormat("0.00000");
		return Double.parseDouble(df.format(d));
	}

}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值