理解 Geohash
Geohash 是 Gustavo Niemeyer 在 2008 年發明的一個地理編碼系統(geocode system),它將經度和緯度這個二維的地理坐標編碼成一個由數字和字母組成的字符串。雖然 geohash 是從經緯度計算出來的,但是 geohash 并不能像經緯度那樣能夠表示出某個點在地圖上的確切位置。實際上,Geohash 表示的是一個區域,這個區域內所有的點都有著相同的 geohash 值。這意味著,geohash 可以幫助用戶隱藏確切的位置信息,從而更好地保護用戶的隱私。雖然我們可以通過 geohash 得知用戶所在的區域,但是我們卻無法知道用戶到底在這個區域中的哪個點。
很多基于位置的個性化服務都是基于 geohash 實現的。比如查找附近的人,尋找附近的餐廳等等。以查找附近的人為例,如果兩個人所處位置的 geohash 相同,那么我們可以認為這兩個人在空間上是相近的。至于具體有多近,這取決于 geohash 所表示的位置精度。通過改變 geohash 的長度,我們可以表示任意精度的位置:geohash 越短,其表示的區域越大,位置精度越低;相反,geohash 越長,其表示的區域越小,位置精度越高。
以天府廣場(latitude: 30.6599157, longitude: 104.0638546)為例,下圖展示了通過不斷增加 geohash 長度提高展示位置精度的過程:
- 當 geohash 長度為 1 時,選擇 w
- 當 geohash 長度為 2 時,選擇 wm
- 當 geohash 長度為 3 時,選擇 wm6
- 當 geohash 長度為 4 時,選擇 wm6n
- 當 geohash 長度為 5 時,選擇 wm6nj
- 當 geohash 長度為 6 時,選擇 wm6nj2
需要注意的是,同一個地點在不同地圖下的經緯度可能是不一樣的。本文采用的是 OpenStreeMap。
經度與緯度
經緯度是由地球表面經線和緯線相交組成的一個坐標系統。每根經線和緯線都有不同的度數,叫經度和緯度。
地球是一個球體,經線連接南北兩極,是半圓弧狀。經過英國首都倫敦格林尼治天文臺原址的那一條經線被定為 0° 經線,又叫本初子午線。本初子午線往東為東半球,往西為西半球。東西兩個半球的經度范圍均在 0° 至 180° 之間,合計360度。一般將西半球的經度范圍記為 [-180°, 0°),而將東半球的為 (0°, 180°]。
緯線與經線垂直的圓圈,任意兩根緯線互相平行。赤道(實際上是地球表面的點隨著地球自轉產生的軌跡中最長的圓周線)是最大的緯線圈,緯度為 0°。赤道將地球分為南北兩個半球,南北兩個半球的緯度范圍都是 90°,合計 180°。從赤道出發,向兩極靠近,緯度越來越大,緯線圈越來越小。一般將南半球的緯度范圍記為 [-90°, 0°),而將北半球的緯度記為 (0°, 90°]。
南極點的緯度記為 90°S(或 -90°),北極點的緯度記為 90°N(或 +90°)。
算法原理
Geohash 是一種將二維的經緯度編碼成一個字符串的地理編碼方法,核心思想是區間二分:將地球編碼看成一個二維平面,然后將這個平面遞歸均分為更小的子塊。
當我們對一個地理坐標進行 geohash 編碼時,先分別計算出經度和緯度各自的二進制編碼,然后按照“從第 0 位開始,偶數位放經度,奇數位放緯度”的規則將經度和緯度的編碼交叉組合,得到一個完整的二進制編碼。接著,將二進制編碼按照五個一組進行劃分,算出每一組二進制編碼的十進制值并將其作為索引查找 base32 編碼表中對應的值。最后將這些值拼接在一起就得到了 geohash 值。
不難看出,geohash 越長,對地圖的劃分次數就越多。劃分的次數多了,矩形區域就小了,位置精度也就上去了。那么是不是 geohash 越長越好呢?當然不是,我們應該根據實際的應用場景來選擇合適的長度。如果使用內存存儲 geohash,geohash 越長,其所占的空間就越大。為了保護用戶的位置隱私,也需要將位置精度控制在合理的范圍內。
接下來還是以成都市天府廣場的位置為例,來看看 geohash 具體是如何計算的。
計算出經度和緯度各自對應的二進制編碼
計算經度或緯度的二進制編碼的方法如下:
首先對緯度進行二進制編碼:
按照這個流程,計算天府廣場緯度 30.6599157 的 15 位二進制編碼的過程:
迭代 左端點 區間中點 右端點 0/1 1 -90.000000 0.000000 90.000000 1 2 0.000000 45.000000 90.000000 0 3 0.000000 22.500000 45.000000 1 4 22.500000 33.750000 45.000000 0 5 22.500000 28.125000 33.750000 1 6 28.125000 30.937500 33.750000 0 7 28.125000 29.531250 30.937500 1 8 29.531250 30.234375 30.937500 1 9 30.234375 30.585938 30.937500 1 10 30.585938 30.761719 30.937500 0 11 30.585938 30.673828 30.761719 0 12 30.585938 30.629883 30.673828 1 13 30.629883 30.651855 30.673828 1 14 30.651855 30.662842 30.673828 0 15 30.651855 30.657349 30.662842 1通過以上計算,緯度 30.6599157 的二進制編碼為:10101 01110 01101。
同理,我們也可以計算出經度 104.0638546 的 15 位二進制編碼:
迭代 左端點 區間中點 右端點 0/1 1 -180.000000 0.000000 180.000000 1 2 0.000000 90.000000 180.000000 1 3 90.000000 135.000000 180.000000 0 4 90.000000 112.500000 135.000000 0 5 90.000000 101.250000 112.500000 1 6 101.250000 106.875000 112.500000 0 7 101.250000 104.062500 106.875000 1 8 104.062500 105.468750 106.875000 0 9 104.062500 104.765625 105.468750 0 10 104.062500 104.414062 104.765625 0 11 104.062500 104.238281 104.414062 0 12 104.062500 104.150391 104.238281 0 13 104.062500 104.106445 104.150391 0 14 104.062500 104.084473 104.106445 0 15 104.062500 104.073486 104.084473 0經度 104.0638546 的二進制編碼為 11001 01000 00000。
交叉合并經度和緯度的二進制編碼
從第 0 位開始,偶數位放經度,奇數位放緯度,得到完整的二進制編碼:
將二進制編碼分組并計算出對應的 Base32 編碼
上面的二進制編碼看起來很長,不方便記憶。為了壓縮編碼長度,geohash 采用了自己的 Base32 編碼,將二進制編碼轉換成方便識別的文本。Geohash 所用的編碼表由數字和字母組成,不過去掉了 a,i,l 和 o 四個字母:
有了編碼表后,我們將之前組合得到的二進制編碼,五個一組,計算出每一組的十進制值,然后查表得到最終的編碼 wm6n2j:
Geohash 解碼
Geohash 的解碼實際上編碼的逆過程,先通過 Base32 編碼表找出每個字符的十進制值,然后將十進制轉為二進制,最后通過二進制計算出對應的區域范圍。
前面我們計算出天府廣場的 geohash 是 wm6n2j,現在將其還原為經緯度:
最后一步將二進制還原為十進制,從左往右遍歷二進制編碼,將當前區間對半劃分,若為 0,取左區間為下一步劃分用的區間,為 1 則將右區間作為下一步劃分用的區間。經度的初始區間為 [-180°, +180°],緯度的初始區間為 [-90°, +90°]。
將二進制編碼的緯度 10101 01110 01101 還原,得到它表示的緯度范圍是 (30.657349, 30.662842):
0/1 最小值 最大值 1 0.000000 90.000000 0 0.000000 45.000000 1 22.500000 45.000000 0 22.500000 33.750000 1 28.125000 33.750000 0 28.125000 30.937500 1 29.531250 30.937500 1 30.234375 30.937500 1 30.585938 30.937500 0 30.585938 30.761719 0 30.585938 30.673828 1 30.629883 30.673828 1 30.651855 30.673828 0 30.651855 30.662842 1 30.657349 30.662842將二進制編碼的經度 11001 01000 00000 還原,得到它表示的經度范圍是 (104.062500, 104.073486):
0/1 最小值 最大值 1 0.000000 180.000000 1 90.000000 180.000000 0 90.000000 135.000000 0 90.000000 112.500000 1 101.250000 112.500000 0 101.250000 106.875000 1 104.062500 106.875000 0 104.062500 105.468750 0 104.062500 104.765625 0 104.062500 104.414062 0 104.062500 104.238281 0 104.062500 104.150391 0 104.062500 104.106445 0 104.062500 104.084473 0 104.062500 104.073486最終,我們得出 wm6n2j 表示的是經度在 (104.062500, 104.073486) 之間,緯度在 (30.657349, 30.662842) 之間的一個矩形區域。
對比天府廣場(latitude: 30.6599157, longitude: 104.0638546),它恰好在計算出來的范圍之內。這個例子很好地說明了 geohash 是如何表示一個區域范圍的。
Geohash 的長度與位置精度
Geohash 的長度對位置的精度有著非常直接的影響。從下面這個表格可以看出,當編碼長度為 1 時,精度高達 2500km,而當編碼長度為 8 時,精度降到了 19m。
| 1 | 2 | 3 | ±23 | ±23 | ±2,500 km |
| 2 | 5 | 5 | ±2.8 | ±5.6 | ±630 km |
| 3 | 7 | 8 | ±0.70 | ±0.70 | ±78 km |
| 4 | 10 | 10 | ±0.087 | ±0.18 | ±20 km |
| 5 | 12 | 13 | ±0.022 | ±0.022 | ±2.4 km |
| 6 | 15 | 15 | ±0.0027 | ±0.0055 | ±0.61 km |
| 7 | 17 | 18 | ±0.00068 | ±0.00068 | ±0.076 km |
| 8 | 20 | 20 | ±0.000085 | ±0.00017 | ±0.019 km |
Geohash 的局限性
Geohash 非常好用,但它還是存在兩個問題:邊界問題和非線性問題。
邊界問題
Geohash 將鄰近搜索(proximity search)轉換為了字符串前綴匹配,和基于經緯度的算法相比,極大地提高了計算效率。由于 geohash 是將地圖劃分為矩形網格,并單獨對每個矩形進行編碼,這就會帶來以下問題。比如下圖中有 A、B、C 三個點,要查找離 B 最近的點。可以發現,距離較遠的 A 和 B 有著相同的 geohash 編碼,而較近的 C 的 geohash 編碼卻有所不同。
這種問題一般出現在邊界上。解決思路很簡單,除了使用目標點的 geohash 進行匹配外,還需要檢查相鄰 8 個格子的 geohash 編碼,這樣才能選出最符合要求的答案。
非線性問題
Geohash 是基于經緯度的,它能反映出兩個點在經緯度上面的距離,但是卻不能反映出實際距離。在不同的緯度下,單位經度所表示的距離是不一樣的。在赤道,單位經度對應的距離為 111.320km,而在 30°N 和 30°S,單位經度對應的距離為 110.852km。
這種非線性問題并不是 geohash 和經緯度系統的問題,而是在于將球體表面的坐標映射到二維平面的坐標的不均勻性。在不同的緯度下,指定長度的 geohash 所表示的矩形區域大小也是不一樣的。矩形用南北方向的高度(height)和東西方向的寬度(width)來衡量。例如在赤道:
| 1 | 4604.5 km | 5003.8 km |
| 2 | 1249.4 km | 625.5 km |
| 3 | 156.4 km | 156.4 km |
| 4 | 39.1 km | 19.5 km |
| 5 | 4.9 km | 4.9 km |
| 6 | 1.2 km | 610.8 m |
| 7 | 152.7 m | 152.7 m |
| 8 | 38.2 m | 19.1 m |
| 9 | 4.8 m | 4.8 m |
| 10 | 1.2 m | 596.5 mm |
| 11 | 149.1 mm | 149.1 mm |
| 12 | 37.3 mm | 18.6 mm |
Blake Haugen 在他的博客 Geohash Size Variation by Latitude 中展示了不同緯度下不同長度的 geohash 所表示的矩形區域的大小。當 geohash 長度相同時,矩形的高度在不同緯度下是相同的,而矩形的寬度在不同緯度下并不相同。這一點從經緯度的劃分上很好理解,假設地球是一個完美的球體,經線圈的周長是相同的,而緯線圈的周長在赤道最大,越靠近兩極越小并不斷趨近于零。
參考資料
總結
以上是生活随笔為你收集整理的理解 Geohash的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3D游戏建模师都学习什么?需要多少时间学
- 下一篇: 潜伏研发群一个月,我发现了程序员不为人知