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,而經緯度相距較大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM