geohash实现(c语言)

来源:互联网 发布:免费直播软件 编辑:程序博客网 时间:2024/06/13 05:52

GeoHash算法

首先,你要Baidu下,找到该算法核心原理,这里摘自网络文档,简单介绍下。

GeoHash算法是通过二分法,经过一定次数的无限逼近,将经纬度的二维坐标浮点值变成一个可排序、可比较的的字符串编码。

在编码中的每个字符代表一个区域,并且前面的字符是后面字符的父区域,即父子字符串有相同的前缀。其算法的过程如下:

地球纬度区间是[-90,90], 如某纬度是39.92324,可以通过下面算法对39.92324进行逼近编码:

1)区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.92324属于右区间[0,90],给标记为1;

2)接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.92324属于左区间 [0,45),给标记为0;

3)递归上述过程39.92324总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167;

4)如果给定的纬度(39.92324)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的进行会产生一个序列1011 1000 1100 01111001,序列的长度跟给定的区间划分次数有关。

纬度范围

划分区间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进行编码的过程也类似:

经度范围

划分区间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

组码

通过上述计算,纬度产生的编码为1011 1000 1100 0111 1001,经度产生的编码为1101 00101100 0100 0100。奇数位放纬度,偶数位放经度,相互穿插,把2条串编码组合生成新串:1110011101001000111100000011010101100001

最后使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,不会base32的网上搜搜。首先将1110011101 00100 01111 00000 01101 01011 00001转成十进制 28,29,4,15,0,13,11,1,十进制对应的编码就是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

由上可知,字符串越长,表示的范围越精确。当GeoHash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右,编码长度需要根据数据情况进行选择。不过从GeoHash的编码算法中可以看出它的一个缺点,位于边界两侧的两点,虽然十分接近,但编码会完全不同。实际应用中,可以同时搜索该点所在区域的其他八个区域的点,即可解决这个问题。


以上原理部分是我从网路上摘录的,以下是我的c实现方法:

/**

 * C语言实现版本

 */

#include <stdio.h>

 

#define BASE32 "0123456789bcdefghjkmnpqrstuvwxyz"

// ASCII码表搬下来,去掉我们不用的字符,将解码要用的下标转换如下

static int UNBASE32[]={0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,11,12,13,14,15,16,0,17,18,0,19,20,0,21,22,23,24,25,26,27,28,29,30,31};

 

/**

 * @param latitude 纬度

 * @param longitude 经度

 * @param precision 字符编码结果的长度(精度)

 * @param geohash 编码结果

 */

void geohash_encode(double latitude,double longitude,int precision,char*geohash){

    char is_odd =0;//是否奇数标志,1奇数 0偶数

    int i =0;//编码计数

    double lats[2];//纬度,lats[0]当前查找区间的左边界,最小值;lats[1]当前查找范围的右边界,最大值;

    double lons[2];//经度,lons[0]当前查找区间的左边界,最小值;lons[1]当前查找范围的右边界,最大值;

    double mid;//对当前查找区间向左或向右折半后,新的边界值。

    char bits[]={0x10,0x08,0x04,0x02,0x01};//用二进制表示,依次为 {00010000,00001000,00000100,00000010,00000001}

    int bit =0;//bits的下标

    char tmp =0;//每计算满5位二进制位后,进行一次Base32编码,然后清零重复计算。这个值就是临时存储5位二进制中间值的。

   

    // 初始化,将查找区间设为最大。

    lats[0]=-90.0;

    lats[1]=90.0;

    lons[0]=-180.0;

    lons[1]=180.0;

    while (i< precision){

        if (is_odd){// 奇数,处理纬度

            mid = (lats[0]+ lats[1])/2;

            if (latitude > mid){

                tmp |= bits[bit];

                lats[0]= mid;

            }

            else

                lats[1]= mid;

        } else{// 偶数,处理经度

            mid = (lons[0]+ lons[1])/2;

            if (longitude > mid){// 落在右侧

                tmp |= bits[bit];//运算,实际是给第i位赋值“1”

                lons[0]= mid;

            }

            else // 落在左侧,给第i位赋值“0”,由于初始化就是0,所以这里不用操作了。

                lons[1]= mid;

        }

        is_odd = !is_odd; //取反

        if (bit<4)

            bit++;

        else { //4bit,进行base32编码,然后对中间值清零

            geohash[i++]= BASE32[tmp];//进行Base32编码

            bit = 0;

            tmp = 0;

        }

    }

    geohash[i]=0;//最后一位置0,数组结束标志

}

 

// 二分查找

int find(char c){

    int start =0;

    int end =31;

    int mid =15;

    for(;;){

        // 二分查找

        mid = (start + end)/2;

        if(BASE32[mid]== c|| start>= end){

            return mid;

        } elseif(c < BASE32[mid]){

            end = mid;//左侧

        } else{

            start = mid;//右侧

        }

    }

}

 

void geohash_decode(double*latitude,double*longitude,int*precision,constchar *geohash){

    char is_even =1;

    char index =0;

    char tmp1 =0;

    int bit =0;

    double lats[2];

    double lons[2];

    double mid;

    char *p= geohash;

   

    *precision =0;

    lats[0]=-90.0;

    lats[1]=90.0;

    lons[0]=-180.0;

    lons[1]=180.0;

    while(0!=*p){

        bit = 0;

        //index = find(*p);

        index = UNBASE32[(*p-'0')];

        while(bit<5){

            tmp1 = (index >> (4-bit))&0x01;//将结果右移到最低位,和1后,取出值

            if(is_even){

                mid = (lons[0]+ lons[1])/2;

                if(tmp1)

                    lons[0]= mid;//右区间

                else

                    lons[1]= mid;

            } else {

                mid = (lats[0]+ lats[1])/2;

                if(tmp1)

                    lats[0]= mid;

                else

                    lats[1]= mid;

            }

            is_even = !is_even;

            bit++;

        }

        //printf("%d %c [%f,%f %f,%f]\n", index, *p, lats[0], lats[1],lons[0], lons[1]);

        (*precision)++;

        p++;

    }

    *latitude =(lats[0]+ lats[1])/2;

    *longitude =(lons[0]+ lons[1])/2;

}

 

int main(){

    double latitude =39.909605;

    double longitude =116.397228;

    int precision =5;

    char geohash[20];

    geohash_encode(latitude, longitude, precision, geohash);

    printf("[%f,%f]编码结果:%s\n", latitude, longitude, geohash);

   

    latitude = 0;

    longitude = 0;

    precision = 0;

    geohash_decode(&latitude,&longitude,&precision, geohash);

    printf("%s解码结果:[%f,%f]%d\n", geohash, latitude, longitude, precision);

    return 0;

}

执行结果:

经度为5的时候

[39.909605,116.397228]编码结果:wx4g0

wx4g0 解码结果:[39.924316,116.389160] 5

经度为10的时候

[39.909605,116.397228]编码结果:wx4g09mf7c

wx4g09mf7c解码结果:[39.909604,116.397223] 10

经过对比,明显能看出精度为10的时候,解码的结果更接近原始经纬度值。


0 0
原创粉丝点击