#1436 : GeoHash一·编码解码
-
3 2 36.255833 117.103367 27.293056 112.688922 34.477861 110.082600 wx0csn0ng82y ww0k7k0et784
样例输出
-
ww7q2b0jd1 wsb5k299d4 wqnk0ux8n7 39.672792 113.730620 34.519663 112.995297
描述
小Hi:上一次我们讲到了在一个城市里,利用四叉树来查找一个坐标附近的点。假如我们把范围扩大到整个地球呢?
小Ho:扩大到整个地球,那坐标怎么办?
小Hi:坐标的话,我们就用经纬度好了。纬度从-90°到90°,经度从-180°到180°。
小Ho:四叉树里面我们用的都是整数坐标,如果变成实数坐标感觉就不那么容易了。
小Hi:没错,而且在扩展到全球的情况下,可以预见点数也会变得非常巨大。因此用四叉树来存储这么多点显然不太现实。
小Ho:那有什么好一点的方法么?
小Hi:当然有了,这一次我们采用编码的方式来解决。
输入
第1行:2个整数N,M。1≤N,M≤100。
第2..N+1行:每行2个实数x,y,第i+1行表示第i个点的纬度和经度。-90≤x≤90,-180≤y≤180
第N+2..N+M+1行:每行1个字符串,表示需要解码的geohash代码
输出
第1..N行:每行1个字符串,第i行表示以第i个点的geohash编码,长度为10。
第N+1..N+M行:每行2个实数x,y,第N+i行表示以第i个geohash编码所表示的区域中心点坐标,保留6位小数。
提示:geohash
小Hi:geohash,由Gustavo Niemeyer提出的一种对地图坐标进行编码的方式。其实现原理采用了分治的思想,将坐标值转化为二进制码。
我们以坐标(39.980778,116.308785)为例,首先对纬度39.980778进行编码:
首先将区间(-90.0,90.0)分为(-90.0,0.0),(0.0,90.0),若纬度属于前者,则二进制码为0,否则为1。由于39.980778属于(0.0,90.0),因此第一位为1。
接下来对(0.0,90.0)再进行一次二分,分为(0.0,45.0),(45.0,90.0),该纬度属于前者,因此第二位为0。
第三次对(0.0,45.0)进行二分,第四次对(22.5,45.0)二分,这样经过多次之后分割之后,我们可以得到一个表示纬度的二进制串:
1011100011011100100011101
同理我们对经度116.308785也进行编码,可以得到表示经度的二进制串:
1101001010110101010111100
在获得经纬度编码后,需要将两个经纬度进行合并,组合为一个二进制编码。
依次取经度和纬度的每一位进行合并(奇数位为经度,偶数位为纬度):
lat: 1 0 1 1 1 0 0 0 1 1 0 1 1 1 0 0 1 0 0 0 1 1 1 0 1 11100111010010001101101101110010011000101111110001 lon: 1 1 0 1 0 0 1 0 1 0 1 1 0 1 0 1 0 1 0 1 1 1 1 0 0
小Ho:这和线段树还挺像的。
小Hi:毕竟都是基于分治的思想嘛。
得到合并的二进制码后,我们将其5位分为一组,并使用base32表来转化为对应的字符。
base32表包含0~9以及小写字母(去除'a','i','l','o'),共32个字符,用以表示32进制数,每个字符对应的10进制值为:
+---------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | base32 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | b | c | d | e | f | g | +---------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | decimal | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | +---------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | base32 | h | j | k | m | n | p | q | r | s | t | u | v | w | x | y | z | +---------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | decimal | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | +---------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
对于我们之前得到的二进制码,其转化为Base32后为:
11100 11101 00100 01101 10110 11100 10011 00010 11111 10001 28 29 4 13 22 28 19 2 31 17 w x 4 e q w m 2 z j
因此得到表示(39.980778,116.308785)的geohash编码为wx4eqwm2zj。
在编码过程中根据二分的次数,来决定二进制码的长度,从而决定最后得到的geohash编码长度。
geohash解码的过程是编码的逆操作,先通过编码还原为二进制码,再分解为经度和纬度的二进制码。最后将经度和纬度的区间不断分割以逼近目标。
需要注意的是,解码过程中求得的区域是一个矩形区域,因此一般采用该区域的中心作为该geohash编码表示的经纬度。
最后给出编码和解码的伪代码:
Encode(latitude, longitude, precision): // precision 表示最后得到的geohash编码长度 latitudeInterval = {-90, 90} longitudeInterval = {-180, 180} length = precision * 5 // 需要的二进制编码长度 geohash = "" // 初始化为空字符串 bits = 0 // 记录二进制码 For i = 1 .. length If i is odd Then // 奇数位为经度 mid = (longitudeInterval[0] + longitudeInterval[1]) / 2 If longitude > mid Then // 二进制码设定为1 bits = bits * 2 + 1 // 更新区间 longitudeInterval[0] = mid Else // 二进制码设定为0 bits = bits * 2 // 更新区间 longitudeInterval[1] = mid End If Else mid = (latitudeInterval[0] + latitudeInterval[1]) / 2 If latitude > mid Then bits = bits * 2 + 1 latitudeInterval[0] = mid Else bits = bits * 2 latitudeInterval[1] = mid End If End If If i mod 5 == 0 Then geohash = geohash + Base32[bits] bits = 0 // 重置二进制码 End If End For Return geohash Docode(geohash): odd = true // 当前计算位的奇偶性 latitudeInterval = {-90, 90} longitudeInterval = {-180, 180} For i = 0 .. geohash.length - 1 bits = Base32.indexOf(geohash[i]) // 找到第i个字符对应的数 For j = 4 .. 0 bit = (bits >> j) & 1 // 通过位运算取出对应的位 If odd Then // 奇数位为经度 mid = (longitudeInterval[0] + longitudeInterval[1]) / 2 longitudeInterval[1 - bit] = mid Else mid = (latitudeInterval[0] + latitudeInterval[1]) / 2 latitudeInterval[1 - bit] = mid End If odd = not odd End For End For latitude = (latitudeInterval[0] + latitudeInterval[1]) / 2 longitude = (longitudeInterval[0] + longitudeInterval[1]) / 2 Return {latitude, longitude}
小Ho:看上去也不是很难的样子,我这就去实现它!
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int shu[500],ans[50];
char ch[50];
void pp1()
{
double x,y,l,r,m;
char da[40]="0123456789bcdefghjkmnpqrstuvwxyz";
scanf("%lf%lf",&x,&y);
l=-90.0;r=90.0;
for (int i=1;i<50;i+=2)
{
m=(l+r)/2;
if (x<=m)
{
r=m;
shu[i]=0;
}
else
{
l=m;
shu[i]=1;
}
}
l=-180.0;r=180.0;
for (int i=0;i<50;i+=2)
{
m=(l+r)/2;
if (y<=m)
{
r=m;
shu[i]=0;
}
else
{
l=m;
shu[i]=1;
}
}
for (int i=0;i<10;i++)
{
ans[i]=0;
for (int j=5*i;j<5*i+5;j++)
{
ans[i]=ans[i]*2+shu[j];
}
}
ch[10]=0;
for (int i=0;i<10;i++)
ch[i]=da[ans[i]];
printf("%s\n",ch);
}
void pp2()
{
char da[40]="0123456789bcdefghjkmnpqrstuvwxyz";
scanf("%s",ch);
int ll=strlen(ch);
for (int i=0;i<ll;i++)
{
for (int j=0;j<32;j++)
{
if (da[j]==ch[i])
{
ans[i]=j;
break;
}
}
}
for (int i=0;i<ll;i++)
{
int j=5*i;
shu[j]=ans[i]&16;
shu[j+1]=ans[i]&8;
shu[j+2]=ans[i]&4;
shu[j+3]=ans[i]&2;
shu[j+4]=ans[i]&1;
}
ll*=5;
double x,y,l,r,m;
l=-90.0;r=90.0;
for (int i=1;i<ll;i+=2)
{
m=(l+r)/2.0;
if (shu[i]==0)
r=m;
else
l=m;
}
x=(l+r)/2.0;
l=-180.0;r=180.0;
for (int i=0;i<ll;i+=2)
{
m=(l+r)/2.0;
if (shu[i]==0)
r=m;
else
l=m;
}
y=(l+r)/2.0;
printf("%.6lf %.6lf\n",x,y);
}
int main()
{
int m,n;
scanf("%d%d",&n,&m);
while (n--)
pp1();
while (m--)
pp2();
return 0;
}