c语言geohash算法,geohash实现(c语言)

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

#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,const char *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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值