一,空间搜索相关:

  基于地图的空间搜索使用经度(longitude)和纬度(latitude)作为基本的搜索条件进行地理信息的相关检索和查询。其实现方式一般包括以下一种实现方式:

    1. 在数据库表中将经度(longitude)和纬度(latitude)分别以两个不同的列进行存储,通过对经度(longitude)和纬度(latitude)两列建二元索引,提供对经度(longitude)和纬度(latitude)的检索。这种方式简单粗暴,可以满足对小规模的数据进行经纬度的检索。但是,在地图搜索相关的应用中,地理信息相关的记录的数据量会很多,二元索引的速度应该不会很理想,再者,基于地理信息的搜索行为相关的搜索基准点相对比较稀疏,数据库的缓存效果也不会太理想。
    2. Geohash:通过这种编码方式,可以将经度(longitude)和纬度(latitude)值转换为一个基于base32编码的串,一方面可以实现对地理信息的一元索引;另外,地理上相近的点通过这种编码方式编码之后所得的串的前缀相同(当然会有特例,也就是一些边界条件,本文后面讲描述;前缀长度根据表示精度的不同而不同)。这样,要实现"搜索给定点附近的其他点"时,只需要将该参照点按照Geohash进行编码,按照精度要求从所得编码串中选取一定长度的字串作为前缀进行搜索,这样就可以很方便的实现该功能;
    3. R-tree:还未具体学习,后续补充

其他支持:

    1. Mysql spatial 扩展
    2. Lucene spatial search

二,Geohash

  在介绍Geohash之前,先简单介绍一下经纬度概念。为了明确表示世界各地在地球上的位置,将地球看成一个基于经纬度线的坐标系。纬线就是平行于赤道平面的那些平面的周线,经线就是连接南北两极的大圆线的半圆弧。纬度分为北纬(正),南纬(负),赤道所在的纬度值为0。经度以本初子午线界(本初子午线经度为0),分为东经(正),西经(负)。故纬度范围可表示为[-90o, 0o),(0o, 90o],经度范围可表示为[-180o, 0o),(0o, 180o]。

  我们可以将经度和纬度看成二维坐标系中的两个纬度,横轴表示经度,纵轴表示纬度,那么所表示的区域如下所示:

  

图 1

  如上图所示,我们可以将整个区域逐步通过两条直线进行细分。例如:首先我们可以将原区域分成[(0, 0),(180, 0),(0, 90),(180, 90)],[(0, 0),(-180, 0),(0, 90),(-180, 90)],[(0, 0),(-180, 0),(0, -90),(-180, -90)],[(0, 0),(180, 0),(0, -90),(180, -90)]等四个区域,我们可以将这四个区域中的点分别按照该点所在的区域进行编码。具体的编码方式为先水平方向(经度)后竖直方向(纬度),以水平方向(经度)为例,它被竖直中轴线一分为二,那么右侧的编码为1,左侧的编码为0;同理,在竖直方向,上测的编码为1,下测的编码为0;依照这种规则,那么处于右上区域的点在第一次划分后的编码为11(如上图中A,E编码后的前两位),处于左上区域的点在第一次划分后的编码为01(如上图中B编码后的前两位),同理,第一次划分后C的编码为00,D的编码为10。

  接下来对第一次划分后的四个区域重复上述过程,以第一次划分后的右上区域为例,可以将这个区域继续分为四个小区域,编码方式和第一次相同,根据这四个小区域在其所处的直接大区域中的方位进行编码,即这四个小区域中右侧的两个小区域水平编码为1(经度),左侧的两个小区域水平编码(经度)为0,上测的两个小区域的竖向编码为1,下测的两个小区域的竖向编码为0。所以,A第二次划分的编码为11,E第二次划分的编码为00。那么经过两次划分之后,A的编码为11 11,E的编码为11 00(即按照划分次序将每次的子编码进行拼接,如A第一次为11,第二次为11,故A两次划分之后编码为1111,同理E为1100)。明白了上述原理之后,我们可以根据上述原理将整个区域继续进行划分,就可以得到给定某一点更精确的编码值。

  上述划分的过程是我个人对Geohash编码思想的一种直观的理解,对任意一对经度、纬度值(lon,lat),我们可以根据上述原理将其编码为0、1所组成的串,然后将所得串按照base32进行编码。

  例如,对于经纬度值(116.3906,39.9232),我们可以将经纬度按照上述原理转换成如下0、1序列

图 2

  

图 3

  所得序列为:

    经度0、1编码后的序列为:1 1 0 1 0 0 1 0 1 1 0 0 0 1 0

    纬度0、1编码后的序列为:1 0 1 1 1 0 0 0 1 1 0 0 0 1 1

    合并经度、纬度0、1编码后的序列得到,经纬度(116.3906,39.9232)的0、1编码序列:

图 4

    注意:

    1. 在所得经纬度编码中,偶数位存放经度编码,奇数为存放纬度编码(编码位序号从0开始计数,所以第一位是偶数位)      

  接下来对所得的经纬度0、1编码后的序列进行base32编码,base32编码对应表如下:

图 5

  所以上述序列经过base32编码后为:wx4g0e,即(116.3906,39.9232)geohash编码为wx4g0e(经度为6)

  

  接下来,我们进一步理解Geohash编码。Geohash其实就是将整个地图或者某个分割所得的区域进行一次划分,由于采用的是base32编码方式,即Geohash中的每一个字母或者数字(如wx4g0e中的w)都是由5bits组成(2^5 = 32, base32),这5bits可以有32中不同的组合(0~31),这样我们可以将整个地图区域分为32个区域,通过00000 ~ 11111来标识这32个区域。第一次对地图划分后的情况如下图所示(每个区域中的编号对应于该区域所对应的编码):

图 6

  也许有些读者对于上图中各区域的编号很难理解,我们不妨从Geohash的编码方式中找原因。因为Geohash的0、1串序列是经度0、1序列和纬度0、1序列中的数字交替进行排列的,如图4所示,在图4中,偶数位对应的序列为经度序列(0标识第一位),奇数位对应的序列为纬度序列,那么在进行第一次划分时,Geohash0、1序列中的前5个bits(11100),那么这5bits中有3bits是表示经度,2bits表示纬度,所以第一次划分时,是将经度划分成8个区段(2^3 = 8),将纬度划分为4个区段(2^2 = 4),这样就形成了32个区域。那么为什么第一次划分后每个区域对应的序号是上图中那样呢?那么下面这个图就可以详细说明。

图 7

  同理,可以按照第一次划分所采用的方式对第一次划分所得的32个区域各自再次划分,如下图所示:

图 8

  细心的读者会发现对第一次划分所得编号为S的区域再次进行划分后,所得的是8x4的32个区域,而不是如同第一次划分中的4x8的32个区域,这是为什么呢?这个也需要从Geohash的0、1序列来解释,因为Geohash的0、1序列的第一个5bits表示第一次划分,第二个5bits表示第二次划分,但是两者不同的是,在第一个5bit中有3bits(0,2,4)表示经度,2bits(1,3)表示纬度,而在第二个5bits中,有2bits(6,8)表示经度,3bits(5,7,9)表示纬度,所以应该是8x4的32个区域。每个区域对应的序号如下图所示:

  

图 9

三,GeoHash encode与decode源码(源代码引用自)

encode源码

 1 function encodeGeoHash(latitude, longitude) {
 2      var is_even=1;
 3      var i=0;
 4      var lat = []; var lon = [];
 5      var bit=0;
 6      var ch=0;
 7      var precision = 12;
 8      geohash = "";
 9  
10      lat[0] = -90.0;  lat[1] = 90.0;
11      lon[0] = -180.0; lon[1] = 180.0;
12  
13      while (geohash.length < precision) {
14          if (is_even) {
15              mid = (lon[0] + lon[1]) / 2;
16              if (longitude > mid) {
17                  ch |= BITS[bit];
18                  lon[0] = mid;
19              } else
20                  lon[1] = mid;
21          } else {
22              mid = (lat[0] + lat[1]) / 2;
23              if (latitude > mid) {
24                      ch |= BITS[bit];
25                      lat[0] = mid;
26              } else
27                  lat[1] = mid;
28          }
29  
30          is_even = !is_even;
31  
32          if (bit < 4)
33              bit++;
34          else {
35              geohash += BASE32[ch];
36              bit = 0;
37              ch = 0;
38          }
39      }
40      return geohash;
41 }

decode源码

 1 function decodeGeoHash(geohash) {
 2     var is_even = 1;
 3     var lat = []; var lon = [];
 4     lat[0] = -90.0;  lat[1] = 90.0;
 5     lon[0] = -180.0; lon[1] = 180.0;
 6     lat_err = 90.0;  lon_err = 180.0;
 7 
 8     for (i=0; i<geohash.length; i++) {
 9         c = geohash[i];
10         cd = BASE32.indexOf(c);
11         for (j=0; j<5; j++) {
12             mask = BITS[j];
13             if (is_even) {
14                 lon_err /= 2;
15                 refine_interval(lon, cd, mask);
16             } else {
17                 lat_err /= 2;
18                 refine_interval(lat, cd, mask);
19             }
20             is_even = !is_even;
21         }
22     }
23     lat[2] = (lat[0] + lat[1])/2;
24     lon[2] = (lon[0] + lon[1])/2;
25 
26     return { latitude: lat, longitude: lon};
27 }

 

 

posted on 2013-01-31 02:28  muson  阅读(3313)  评论(0编辑  收藏  举报