兄弟篇:【搬磚筆記】 利用GeoHash爲地理位置編碼——實現篇html
最近有個需求,要計算出客戶座標附近5千米的全部門店,並按照步行距離排序。算法
最直接的方法就是遍歷該城市下的全部門店,可是該方法明顯不可取,由於在門店數量巨大,且還須要計算步行距離並排序的狀況下,時間複雜度太高。post
忽然想到當年作圖像碰見一個問題:給定一個視頻中連續的三千幀,可是已經亂序,告訴你第一幀,將這三千幀進行排序。遍歷圖像的全部像素點一樣不可取,當時的解決方案是利用感知哈希計算出全部圖像的指紋,接着利用明氏距離計算距離第一張最近的圖像做爲第二張,而後計算距離第二張最近的做爲第三張,以此類推。編碼
一樣,確定也有哈希方法可將地理位置轉換成某種編碼,而且編碼可用於地理計算。它就是 GeoHash。.net
進入正文以前,先一塊兒回顧一下初中地理:code
本初子午線以西爲西經,分十二區,每區15度共180度;以東爲東經,一樣分十二區,共180度。cdn
赤道以北爲北緯,共90度;以南爲南緯,一樣90度。視頻
那麼在計算機中,座標表示爲:htm
西經爲負數,東經爲正數,所以經度取值
[-180, 180]
。blog北緯爲正數,南緯爲負數,所以緯度取值
[-90, 90]
。
咱們知道赤道長約四萬千米,所以經度上每度約等於111千米。地球其實是個不規則球體,但爲了簡便計算,咱們假設把緯度上每度約等於222千米。
首先計算二進制編碼,經度上以[-180, 180]
開始,緯度以[-90, 90]
開始,每次將區間進行二分,若輸入座標小於中間值則編碼爲0
,下次區間取左半區間;若大於則編碼爲1
,下次區間取右半區間。以此類推,編碼越長,越接近座標值,進而越精確。
以 118°04′04, 24°26′46
爲例,首先計算經度的編碼:
編碼長度 | 區間 | 中間值 | 編碼 | 說明 | 精度(公里) |
---|---|---|---|---|---|
1 | [-180, 180] | 0 | 1 | 118°04′04 大於 0°,所以編碼1,取右區間 | 19980 |
2 | [0, 180] | 90 | 1 | 118°04′04 大於 90° | 9990 |
3 | [90, 180] | 135 | 0 | 118°04′04 小於 135°,所以編碼0,取左區間 | 4995 |
4 | [90, 135] | 112.5 | 1 | 2497.5 | |
5 | [112.5, 135] | 123.75 | 0 | 1248.75 | |
6 | [112.5, 123.75] | 118.125 | 0 | 624.375 | |
7 | [112.5, 118.125] | 115.3125 | 1 | 312.188 | |
8 | [115.3125, 118.125] | 116.71875 | 1 | 156.094 | |
9 | [116.71875, 118.125] | 117.421875 | 1 | 78.047 | |
10 | [117.421875, 118.125] | 117.7734375 | 1 | 39.024 | |
N | ... | ... | . | ... |
一樣道理,計算緯度得編碼:
編碼長度 | 區間 | 中間值 | 編碼 | 說明 | 精度(公里) |
---|---|---|---|---|---|
1 | [-90, 90] | 0 | 1 | 24°26′46 大於 0°,所以編碼1,取右區間 | 19980 |
2 | [0, 90] | 45 | 0 | 24°26′46 小於 45°,所以編碼0,取左區間 | 9990 |
3 | [0, 45] | 22.5 | 1 | 4995 | |
4 | [22.5, 45] | 33.75 | 0 | 2497.5 | |
5 | [22.5, 33.75] | 28.125 | 0 | 1248.75 | |
6 | [22.5, 28.125] | 25.3125 | 0 | 624.375 | |
7 | [22.5, 25.3125] | 23.906 | 1 | 312.188 | |
8 | [23.906, 25.3125] | 24.609 | 0 | 156.094 | |
9 | [23.906, 24.609] | 24.2575 | 1 | 78.047 | |
10 | [24.2575, 24.609] | 24.433 | 0 | 39.024 | |
N | ... | ... | . | ... |
綜上,假設咱們只取十位編碼,經度上獲得得編碼爲 1101001111
,緯度上編碼爲 1010001010
。
將兩個編碼就像經緯交織網同樣進行交織:
最後獲得經緯二進制編碼爲11100110000011101110
,結合以上兩表的精度數據,咱們知道該編碼其實表明的是一塊長寬爲39.024公里的矩形塊。
二進制編碼其實就能夠用來做爲地理位置編碼,可是:
所以,GeoHash 採用了 base32 和 base36編碼,由於大多數採用 base32 編碼,所以本文僅介紹 base32 編碼。
Decimal | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Base 32 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | B | C | D | E | F | G |
Decimal | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Base 32 | H | J | K | M | N | P | Q | R | S | T | U | V | W | X | Y | Z |
base32 編碼共 32 個編碼,所以須要 5 個bit,將11100110000011101110
轉換爲 base32 編碼:
WS7F
。
通用法的輸入是二進制編碼,所以不像查表法有位數限制,適用於全部狀況,流程簡單。
首先將經緯交織的二進制編碼拆分爲經度、緯度,若要求輸入位置以北,則將緯度加一;以南,則將緯度減一;以西,則將經度減一;以東,則將經度加一。須要注意的是,加減後必須保持原有的有效位數(好比,11 + 01 = 00,保持兩位有效位)。最後將計算結果從新組合,獲得要求位置的二進制編碼。
過程以下圖爲例,再也不贅述:
查表法的計算比通用法快不少,不用將輸入編碼拆分紅經緯度進行加減,但須要注意的是:輸入的是base32編碼,所以僅適用於二進制編碼位數爲5的倍數的GeoHash
。
網上大部分文章僅講訴如何利用查表法計算鄰塊,那麼這個查表如何獲得呢? 獲得了編碼後,如何計算鄰塊?網上大部分文章僅講訴如何利用查表法計算鄰塊,可是這個查表如何獲得呢?
根據前面,咱們知道編碼由5個bit二進制,並經緯交織組成。所以,若該編碼處於奇數位上,即 經 緯 經 緯 經
交織方式;若處於偶數位上,則 緯 經 緯 經 緯
交織方式。
那麼,以 W
爲例,二進制爲 11100
,若處於奇數位則在表中爲 右 上 右 下 左
,若處於偶數位則在表中爲 上 右 下 左 下
,所以 W
在查表中的位置以下圖:
WS7F
爲例,如今要求該位置的鄰近塊,取末位
F
,從偶數位表中能夠看到,
F
的鄰近爲
E, G, D, 9, C
,以及往右出了界的
5, 4, 1
。所以
5, 4, 1
三個鄰塊還須要看倒數第二位
7
。從奇數表中能夠看到,
7
的右邊沒有超界(若超界了,看倒數第三位,以此類推),爲
K
。
所以,獲得周圍鄰塊分別位 WS7E, WS7G, WS7D, WS79, WS7C, WSK5, WSK4, WSK1
,位置關係以下:
主要從理論上介紹:
算法實現:【搬磚筆記】 利用GeoHash爲地理位置編碼——實現篇
GeoHash 不只能夠用來對位置點進行編碼,也能夠用來對面進行編碼,有助於處理點、面的多種組合關係計算。好比,判斷點位置是否在門店的配送圍欄以內。
GeoHash 其實就是 Peano 曲線 的一種應用,以下:
還有許多空間填充曲線,好比公認比較好的,沒有較大突變的 Hilbert 曲線。