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,而經緯度相距較大。如(‘22.540092’, ‘0.’)的geohash=sh000p21818n;而(‘22.540092’, ‘-0.’)的的geohash=eupbpzrcxcxy
3、python代碼和java代碼爲什麼編碼的geohash會出現不一致
如同樣的(‘22.540092’, ‘0’),可以認爲是(‘22.540092’, ‘-0.’),也可以認爲是(‘22.540092’, ‘0.’);在python裏面會認爲是(‘22.540092’, ‘-0.’),在java裏面會認爲是’22.540092’, ‘0’

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章