【搬磚筆記】 利用GeoHash為地理位置編碼

PatrickLin發表於2019-05-12

一、前言

最近有個需求,要計算出客戶座標附近5公里的所有門店,並按照步行距離排序。

最直接的方法就是遍歷該城市下的所有門店,但是該方法明顯不可取,因為在門店數量巨大,且還需要計算步行距離並排序的情況下,時間複雜度過高。

突然想到當年做影像遇見一個問題:給定一個視訊中連續的三千幀,但是已經亂序,告訴你第一幀,將這三千幀進行排序。遍歷影像的所有畫素點同樣不可取,當時的解決方案是利用感知雜湊計算出所有影像的指紋,接著利用明氏距離計算距離第一張最近的影像作為第二張,然後計算距離第二張最近的作為第三張,以此類推。

同樣,肯定也有雜湊方法可將地理位置轉換成某種編碼,並且編碼可用於地理計算。它就是 GeoHash

二、相關知識

進入正文之前,先一起回顧一下初中地理:

本初子午線以西為西經,分十二區,每區15度共180度;以東為東經,同樣分十二區,共180度。

赤道以北為北緯,共90度;以南為南緯,同樣90度。

那麼在計算機中,座標表示為:

西經為負數,東經為正數,因此經度取值[-180, 180]

北緯為正數,南緯為負數,因此緯度取值[-90, 90]

我們知道赤道長約四萬公里,因此經度上每度約等於111公里。地球實際上是個不規則球體,但為了簡便計算,我們假設把緯度上每度約等於222公里。

三、初識 GeoHash

1. 計算二進位制編碼

首先計算二進位制編碼,經度上以[-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千米的矩形塊。

2. 轉換base32編碼

二進位制編碼其實就可以用來作為地理位置編碼,但是:

  1. 不便於查詢周邊鄰塊。計算鄰塊,需要解析成經、緯兩個編碼再做計算。而採用base32編碼後,可用查表法,加速計算。
  2. 二進位制編碼長度過長,不利於檢索。

因此,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

四、計算鄰近塊

得到了編碼後,如何計算鄰塊?網上大部分文章僅講訴如何利用查表法計算鄰塊,但是這個查表如何得到呢?

根據前面,我們知道編碼由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,位置關係如下:

在這裡插入圖片描述

五、總結

主要從理論上介紹:

  1. GeoHash對地理位置點進行編碼的方法:根據“遞迴二分”獲取二進位制,將二進位制轉換為 Base32 編碼。
  2. GeoHash鄰塊的快速計算方法:取末位編碼,根據偶數位表查詢對應鄰塊編碼,若某方向出界,則查詢前一位編碼,並根據奇/偶表查詢相應方向的編碼。

具體實現後繼釋出。

六、延申

  1. GeoHash 不僅可以用來對位置點進行編碼,也可以用來對面進行編碼,有助於處理點、面的多種組合關係計算。比如,判斷點位置是否在門店的配送圍欄之內。

  2. GeoHash 其實就是 Peano 曲線 的一種應用,如下:

    在這裡插入圖片描述

    還有許多空間填充曲線,比如公認比較好的,沒有較大突變的 Hilbert 曲線

在這裡插入圖片描述

參考

  1. GeoHash核心原理解析
  2. 基於快速GeoHash,如何實現海量商品與商圈的高效匹配?

相關文章