python geohash算法逆地址编码原理初探

1、geohash有什么用途呢?
这几天刚好有个测试任务是关于设备信息位置处理的,里面提及到geohash;抱着测试的警觉性,打算研读一下这个geohash到底是什么?Geohash 是一种地理编码系统,地球上的任何一个物体可以通过经纬度来定位其在地球位置,而作为程序猿通过经纬度两个信息很难(或者说很麻烦)在数据层面上进行检索和比对,这个时候geohash编码系统出现了,更可以说geohash是一种算法可以把经纬度坐标转换为短字符串。当所有的位置信息都可以通过一个字符串代替时,大大提高了地址检索和比对的效率,通过一个字符串可以知道你的位置信息,广泛应用于定位服务和餐饮服务。同时通过字符串比对可以知道所处位置附近的地址信息。

2、python-geohash如何安装
python3安装python-geohash时一直报错无法安装,但是可以安装geohash,安装完geohash时引用模块会ImportError: No module named ‘geohash’报错,解决方法:
找到site-packages将里面的Geohash文件夹改为geohash,同时在文件夹内部的__init__文件内容改为

from .geohash import decode_exactly, decode, encode

 

 3、geohash源码文件
这里先贴出整个geohash精简源码,预览一下

from math import log10
__base32 = '0123456789bcdefghjkmnpqrstuvwxyz'
__decodemap = { }
for i in range(len(__base32)):
    __decodemap[__base32[i]] = i
del i

def decode_exactly(geohash):
    lat_interval, lon_interval = (-90.0, 90.0), (-180.0, 180.0)
    lat_err, lon_err = 90.0, 180.0
    is_even = True
    for c in geohash:
        cd = __decodemap[c]
        for mask in [16, 8, 4, 2, 1]:
            if is_even: 
                lon_err /= 2
                if cd & mask:
                    lon_interval = ((lon_interval[0]+lon_interval[1])/2, lon_interval[1])
                else:
                    lon_interval = (lon_interval[0], (lon_interval[0]+lon_interval[1])/2)
            else:      
                lat_err /= 2
                if cd & mask:
                    lat_interval = ((lat_interval[0]+lat_interval[1])/2, lat_interval[1])
                else:
                    lat_interval = (lat_interval[0], (lat_interval[0]+lat_interval[1])/2)
            is_even = not is_even
    lat = (lat_interval[0] + lat_interval[1]) / 2
    lon = (lon_interval[0] + lon_interval[1]) / 2
    return lat, lon, lat_err, lon_err

def decode(geohash):
    lat, lon, lat_err, lon_err = decode_exactly(geohash)
    lats = "%.*f" % (max(1, int(round(-log10(lat_err)))) - 1, lat)
    lons = "%.*f" % (max(1, int(round(-log10(lon_err)))) - 1, lon)
    if '.' in lats: lats = lats.rstrip('0')
    if '.' in lons: lons = lons.rstrip('0')
    return lats, lons

def encode(latitude, longitude, precision=12):
    lat_interval, lon_interval = (-90.0, 90.0), (-180.0, 180.0)
    geohash = []
    bits = [ 16, 8, 4, 2, 1 ]
    bit = 0
    ch = 0
    even = True
    while len(geohash) < precision:
        if even:
            mid = (lon_interval[0] + lon_interval[1]) / 2
            if longitude > mid:
                ch |= bits[bit]
                lon_interval = (mid, lon_interval[1])
            else:
                lon_interval = (lon_interval[0], mid)
        else:
            mid = (lat_interval[0] + lat_interval[1]) / 2
            if latitude > mid:
                ch |= bits[bit]
                lat_interval = (mid, lat_interval[1])
            else:
                lat_interval = (lat_interval[0], mid)
        even = not even
        if bit < 4:
            bit += 1
        else:
            geohash += __base32[ch]
            bit = 0
            ch = 0
    return ''.join(geohash)

 

整个算法通过代码的形式就只有不到100行,里面涵盖了正逆地址编码,这里主要看一下逆地址编码算法是如何实现字符串转换为经纬度的。

__base32 = '0123456789bcdefghjkmnpqrstuvwxyz'
__decodemap = { }
for i in range(len(__base32)):
__decodemap[__base32[i]] = i
del i

 


这段代码的主要作用就是将字符串赋予一个序号如这样,在最后将残余的i删除掉,这一步可以看出作者写代码的规范还是很好的,值得学习!

def decode_exactly(geohash):
    lat_interval, lon_interval = (-90.0, 90.0), (-180.0, 180.0)
    lat_err, lon_err = 90.0, 180.0
    is_even = True
    for c in geohash:
        cd = __decodemap[c]
        for mask in [16, 8, 4, 2, 1]:
            if is_even: 
                lon_err /= 2
                if cd & mask:
                    lon_interval = ((lon_interval[0]+lon_interval[1])/2, lon_interval[1])
                else:
                    lon_interval = (lon_interval[0], (lon_interval[0]+lon_interval[1])/2)
            else:      
                lat_err /= 2
                if cd & mask:
                    lat_interval = ((lat_interval[0]+lat_interval[1])/2, lat_interval[1])
                else:
                    lat_interval = (lat_interval[0], (lat_interval[0]+lat_interval[1])/2)
            is_even = not is_even
    lat = (lat_interval[0] + lat_interval[1]) / 2
    lon = (lon_interval[0] + lon_interval[1]) / 2
    return lat, lon, lat_err, lon_err

 


decode_exactly主要是将geohash解码为它的确切值,包括错误结果的边距。返回四个浮点值:纬度、经度、纬度的正负误差(为正)、经度的正负误差(为正)。
1、先遍历geohash字符串得到每一个字符对应的十进制序号。如k:18 10010
2、判断语句if is_even+mask使整个函数体默认开始是取经度信息(所以在地址编码时偶数位放经度序列奇数为放维度序列合并为二进制字符然后base32编码得到geohash,这里的偶数位是从0开始;扩展如 北京(39.928167 ,116.389550) 编码后(10111 00011 , 11010 01011) , 组码后 :11100 11101 00100 01111 , base32编码后得到最后的geohash值是wx4g)
3、然后通过cd & mask按位与运算符得到当前区间是前半部分还是后半部分(二分法)
4、mask循环体下通过is_even = not is_even实现切换经纬度信息获取机制
5、通过不断的二分规则知道不能在分得到纬度、经度、纬度的正负误差(为正)、经度的正负误差(为正)

def decode(geohash):
    lat, lon, lat_err, lon_err = decode_exactly(geohash)
    lats = "%.*f" % (max(1, int(round(-log10(lat_err)))) - 1, lat)
    lons = "%.*f" % (max(1, int(round(-log10(lon_err)))) - 1, lon)
    if '.' in lats: lats = lats.rstrip('0')
    if '.' in lons: lons = lons.rstrip('0')
    return lats, lons

这段为逆地址编码主函数,通过表达式%.*f来决定数值的精度有多少为,通过if '.' in lats: lats = lats.rstrip('0')去除尾部的数值0,及2.3000=2.3
至此逆地址源码解析完成,而地址编码其实就是反过来而已。二分法的具体示意图如下

在这段源码中我们需要得到什么呢?
1、一种二分法的使用思路,通过奇数偶数位相错的二进制组合将两个信息合成一个信息然后编码实现可观性字符串
2、通过二分法不断细分保留了所需要的精度值
3、代码精简采用了独特的is_even = not is_even和for mask in [16, 8, 4, 2, 1]来不断的切换奇偶位置
 4、geohash应用讨论
1、通过geohash可以详细的知道位置信息
通过源码我们会发现在逆地址解码时存在一定的经纬度数据误差,这就导致了geohash实际表示的是一种很小的范围而不是精准的位置信息,也有助于保护隐私
2、geohash越相近、经纬度越相近
通过逆地址解码源码我们可以知道,解码时时不断的通过二分法对整个平面不断的细分为更小的平面,这就导致会出现平面右下角和平面左下角的值相近的geohash,而经纬度相距较大。

posted @ 2019-12-09 20:30  pujen_yuan  阅读(3282)  评论(0编辑  收藏  举报