geohash实现(c语言)
2015-10-27 00:32
162 查看
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,序列的长度跟给定的区间划分次数有关。
同理,地球经度区间是[-180,180],对经度116.3906进行编码的过程也类似:
组码
通过上述计算,纬度产生的编码为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。同理,将编码转换成经纬度的解码算法与之相反,具体不再赘述。
由上可知,字符串越长,表示的范围越精确。当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,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的时候,解码的结果更接近原始经纬度值。
首先,你要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 |
经度范围 | 划分区间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 |
以上原理部分是我从网路上摘录的,以下是我的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,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的时候,解码的结果更接近原始经纬度值。
相关文章推荐
- C++格式化输出,C++输出格式控制
- c++中const用法大全与实质剖析
- 输入10个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写三个函数; ①输入10个数;②进行处理;③输出10个数。
- C++ RAII手法实例,不使用智能指针
- C++中的函数重载、覆盖与隐藏
- C++ 类访问控制(public/protected/private)
- 如何用visual studio2013编写简单C语言程序
- C++:主要几种排序算法及其复杂度
- C++:delete和delete[]释放内存的区别
- C++:四种必须使用初始化列表情况
- C++:获取数组长度
- C++:构造函数默认的参数声明
- C++:构造函数的默认参数知识拓展
- C++嵌套类与局部类
- C++嵌套类与局部类
- C++ 实现类似Notepad++ Ctrl+F 的搜索功能
- C++:文件的输入和输出
- C++面试常见题目问与答(汇总一)
- C++Primer笔记一
- C++:流类库与输入输出