`
iamzhongyong
  • 浏览: 805458 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

【原】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

 

纬度范围

划分区间0

划分区间1

39.92324所属区间

(-90, 90)

(-90, 0.0)

(0.0, 90)

1

(0.0, 90)

(0.0, 45.0)

(45.0, 90)

0

(0.0, 45.0)

(0.0, 22.5)

(22.5, 45.0)

1

(22.5, 45.0)

(22.5, 33.75)

(33.75, 45.0)

1

(33.75, 45.0)

(33.75, 39.375)

(39.375, 45.0)

1

(39.375, 45.0)

(39.375, 42.1875)

(42.1875, 45.0)

0

(39.375, 42.1875)

(39.375, 40.7812)

(40.7812, 42.1875)

0

(39.375, 40.7812)

(39.375, 40.0781)

(40.0781, 40.7812)

0

(39.375, 40.0781)

(39.375, 39.7265)

(39.7265, 40.0781)

1

(39.7265, 40.0781)

(39.7265, 39.9023)

(39.9023, 40.0781)

1

(39.9023, 40.0781)

(39.9023, 39.9902)

(39.9902, 40.0781)

0

(39.9023, 39.9902)

(39.9023, 39.9462)

(39.9462, 39.9902)

0

(39.9023, 39.9462)

(39.9023, 39.9243)

(39.9243, 39.9462)

0

(39.9023, 39.9243)

(39.9023, 39.9133)

(39.9133, 39.9243)

1

(39.9133, 39.9243)

(39.9133, 39.9188)

(39.9188, 39.9243)

1

(39.9188, 39.9243)

(39.9188, 39.9215)

(39.9215, 39.9243)

1

 

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

 

经度范围

划分区间0

划分区间1

116.3906所属区间

(-180, 180)

(-180, 0.0)

(0.0, 180)

1

(0.0, 180)

(0.0, 90.0)

(90.0, 180)

1

(90.0, 180)

(90.0, 135.0)

(135.0, 180)

0

(90.0, 135.0)

(90.0, 112.5)

(112.5, 135.0)

1

(112.5, 135.0)

(112.5, 123.75)

(123.75, 135.0)

0

(112.5, 123.75)

(112.5, 118.125)

(118.125, 123.75)

0

(112.5, 118.125)

(112.5, 115.312)

(115.312, 118.125)

1

(115.312, 118.125)

(115.312, 116.718)

(116.718, 118.125)

0

(115.312, 116.718)

(115.312, 116.015)

(116.015, 116.718)

1

(116.015, 116.718)

(116.015, 116.367)

(116.367, 116.718)

1

(116.367, 116.718)

(116.367, 116.542)

(116.542, 116.718)

0

(116.367, 116.542)

(116.367, 116.455)

(116.455, 116.542)

0

(116.367, 116.455)

(116.367, 116.411)

(116.411, 116.455)

0

(116.367, 116.411)

(116.367, 116.389)

(116.389, 116.411)

1

(116.389, 116.411)

(116.389, 116.400)

(116.400, 116.411)

0

(116.389, 116.400)

(116.389, 116.394)

(116.394, 116.400)

0

 

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

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

十进制

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

base32

0

1

2

3

4

5

6

7

8

9

b

c

d

e

f

g

十进制

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

base32

h

j

k

m

n

p

q

r

s

t

u

v

w

x

y

z

 

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

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

 

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

1.两个离的越近,geohash的结果相同的位数越多,对么?
这一点是有些用户对geohash的误解,虽然geo确实尽可能的将位置相近的点hash到了一起,可是这并不是严格意义上的(实际上也并不可能,因为毕竟多一维坐标),
例如在方格4的左下部分的点和大方格1的右下部分的点离的很近,可是它们的geohash值一定是相差的相当远,因为头一次的分块就相差太大了,很多时候我们对geohash的值进行简单的排序比较,结果貌似真的能够找出相近的点,并且似乎还是按照距离的远近排列的,可是实际上会有一些点被漏掉了。
上述这个问题,可以通过搜索一个格子,周围八个格子的数据,统一获取后再进行过滤。这样就在编码层次解决了这个问题。
2.既然不能做到将相近的点hash值也相近,那么geohash的意义何在呢?
我觉得geohash还是相当有用的一个算法,毕竟这个算法通过无穷的细分,能确保将每一个小块的geohash值确保在一定的范围之内,这样就为灵活的周边查找和范围查找提供了可能。

 

常见的一些应用场景

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

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

 

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

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

 

 

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

 

 

import java.io.File;
import java.io.FileInputStream;
import java.util.BitSet;
import java.util.HashMap;


public class Geohash {

	private static int numbits = 6 * 5;
	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{

		System.out.println(new Geohash().encode(45, 125));
			
	}
	
	public 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[] {lat, lon};		
	}
	
	private 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));
	}

}
 

上面提高了根据经纬度来计算距离,这里也贴一下根据经纬度信息来计算距离的实现,其实就是球面上计算两点间的距离。不过稍微有点问题,就是地球其实是椭圆的。

 

 

public class GeoTool {
	
	private static final double EARTH_RADIUS = 6378.137;

	/**
	 * <pre>两点计算距离,传入两点的经纬度,</pre>
	 */
	public static double getPointDistance(double lat1,double lng1,double lat2,double lng2){
		double result = 0 ;
		
		double radLat1 = radian(lat1);
		
		double ratlat2 = radian(lat2);
		double a = radian(lat1) - radian(lat2);
		double b = radian(lng1) - radian(lng2);
		
		result = 2*Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2)+Math.cos(radLat1)*Math.cos(ratlat2)*Math.pow(Math.sin(b/2), 2)));
		result = result*EARTH_RADIUS;	
	
		result = Math.round(result*1000);	//返回的单位是米,四舍五入
		
		return result;
	}
	
	/**由角度转换为弧度*/ 
	private static double radian(double d){
		return (d*Math.PI)/180.00;
	}

	public static void main(String[] args) {
		GeoTool tool = new GeoTool();
		
		System.out.println(tool.getPointDistance(30.27872, 120.12161, 30.27911, 120.12161));

	}

}
 

 

 

 *在纬度相等的情况下:

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

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

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

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

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

 *在经度相等的情况下:

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

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

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

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

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

 

 

 

 

 

参考文章:

http://www.cnblogs.com/step1/archive/2009/04/22/1441689.html

http://tech.idv2.com/2011/07/05/geohash-intro/

http://en.wikipedia.org/wiki/Geohash

http://tech.idv2.com/2011/06/17/location-search/

 

geohash的java代码实现:

http://code.google.com/p/geospatialweb/source/browse/#svn/trunk/geohash/src

 

分享到:
评论
1 楼 langcaiye 2016-10-30  
有2个问题请教:
1. 这里的base32算法为什么需要以负数的形式进行?
2. 这里的base32从调用的地方传入的参数是不可能为负的吧?

相关推荐

    Java实现GeoHash算法

    Java实现GeoHash算法是一种在IT领域中用于地理位置数据存储和检索的技术。GeoHash将经纬度坐标转换为字符串,使得地理位置可以被高效地索引和查询。这种算法利用了空间分割和编码策略,使得相邻的位置在编码后具有...

    geohash算法实现Java代码

    GeoHash算法是一种基于地理坐标的分布式空间索引技术,它通过将地球表面的经纬度坐标转化为可比较的字符串,使得我们可以高效地进行地理位置的搜索、范围查询以及邻居查找等操作。这种算法尤其适用于大数据和分布式...

    geohash算法实现

    Geohash算法实现,经纬度到geohash编码的实现

    geohash算法mysql版代码

    网上有很多geohash算法的实现,都是基于java或者php代码实现的,没有sql实现的版本,这里使用mysql简单实现了这个算法

    Geohash 算法的純 C 實現 将所在地球位置经纬度编解码為一定格式字串 有志於開發外送派單工程師請享用~~

    Geohash算法就是将经纬度编码,将二维变一维,给地址位置分区的一种算法 此檔案為C語言實現 函式庫使用介紹: 1)編碼 char* geohash_encode(double lat, double lng, int precision); 以所需精度獲取緯度和經度並...

    GeoHash核心原理解析

    GeoHash编码算法的步骤包括: 1. 根据给定的经纬度坐标,将地球的纬度和经度区间分别进行二分,然后逐步逼近实际的经纬度值,使用二进制编码记录每次区间划分时的左或右区间。 2. 将得到的纬度和经度二进制编码交替...

    nodejs geohash

    总之,"nodejs geohash"是Node.js开发中处理地理信息的一种有效工具,它利用Geohash算法将经纬度数据转化为便于操作的字符串,从而简化了空间数据的处理过程。结合ngeohash库,开发者可以在Node.js环境中轻松地实现...

    非常使用的 基于geohash 找最近位置java代码

    非常使用的 基于geohash 找一定范围内的 最近位置java代码

    地理坐标 GEOHASH示例代码 geohash.zip

    项目中使用的 GEOhash 算法, 在网上公开的GEOhash demo基础上, 做了升级, 功能: 1. 根据指定坐标生成 GEOhash对象 2. 根据当前坐标(GEOhash对象)获取周边8/9个GEOhash对象 3. [升级]根据当前坐标获取指定半径...

    GEOHASH Javascript的实现

    2. `geohash-demo.js`:包含`GEOHASH`的JavaScript实现代码,可能包括编码、解码以及相邻`GEOHASH`的计算功能。 3. `labeledmarker.js`:可能是一个辅助库,用于在地图上绘制带有标签的标记,用于展示`GEOHASH`对应...

    python_geohash-0.8.5-cp39-cp39-win_amd64.whl.zip

    Python Geohash是一个开源库,它实现了基于Geohash算法的功能,Geohash是一种地理编码技术,能够将地理位置转换为字符串,便于存储和查询。这种编码方式可以高效地进行地理空间数据的索引和搜索,广泛应用于地图服务...

    Android-java中的Geohash工具类

    在Android开发中,Geohash是一种非常实用的地理编码技术,它通过将地理位置转换为字符串,使得我们可以方便地存储、查询和比较这些位置数据。Java中的Geohash工具类可以帮助开发者处理与地理位置相关的任务,提高...

    geohash:一个解决计算附近距离的php类库.zip

    《使用Geohash进行地理位置计算与搜索》 在IT领域,尤其是在Web开发中,地理位置服务已经成为不可或缺的一部分。当用户需要查找附近的餐厅、酒店或者任何商业点时,这就涉及到地理位置的计算和搜索。PHP作为一种...

    python_geohash-0.8.5-cp312-cp312-win_amd64.whl.zip

    Python Geohash是一个用于处理地理坐标数据的Python库,它实现了地理位置编码和解码功能,主要基于Geohash算法。这个特定的版本是`0.8.5`,专为Python 3.12编译,并且适用于Windows操作系统,64位架构(amd64)。`...

    如何找到周围8个区域的GeoHash编码

    在Java中实现找到周围8个区域的GeoHash编码涉及到地理空间索引和定位算法的应用。以下是对这个主题的详细解释: GeoHash的工作原理: GeoHash的基本思想是通过将地球表面划分为网格,并对每个网格分配一个唯一的...

    geohash:一个解决计算附近距离的php类库

    5. **整合进业务逻辑**:将`geohash`与你的应用程序结合,例如在用户注册时记录他们的位置,然后在搜索或推荐场景中使用`geohash`进行高效的定位服务。 总的来说,`geohash`为PHP开发者提供了一种强大的工具,它...

    geohash-1.3.0.jar

    1)GeoHash用一个字符串表示经度和纬度两个坐标,比如我现在所在位置的GeoHash值为 wx4sv61q; 2)GeoHash标识的并不是一个点,而是一个区域,比如 wx4sv61q 对应的就是一个矩形区域; 3)编码的前缀可以标识更大...

    geohash经纬度转换包linux

    其中,Geohash是一种广泛使用的地理位置编码技术,它基于二进制空间划分的算法,将经纬度坐标转化为字符串,方便数据库管理和空间索引。在Python编程语言中,存在许多第三方库用于实现Geohash的编码和解码。本压缩包...

    geohash.jar geohash-1.3.0

    geohash官方发布以及maven发布的版本都是基于jdk1.7编译的,碰到jdk1.6的项目会报unsupported major.minor version 51.0错误。这个资源是我基于jdk1.6编译的,执行测试案例都通过了。

    python_geohash-0.8.5-cp36-cp36m-win_amd64.whl.zip

    5. 查询:利用`geohash.neighbors(geohash_string)`获取邻居GeoHash,以及`geohash.boundingBox(geohash_string)`获取GeoHash所代表的矩形边界。 这个库在地理信息系统(GIS)、地图应用、定位服务和社交网络等领域...

Global site tag (gtag.js) - Google Analytics