GeoHash核心原理解析
引子
機機是個好動又好學的孩子,平日里就喜歡拿著手機地圖點點按按來查詢一些好玩的東西。某一天機機到北海公園游玩,肚肚餓了,于是乎打開手機地圖,搜索北海公園附近的餐館,并選了其中一家用餐。
飯飽之后機機開始反思了,地圖后臺如何根據自己所在位置查詢來查詢附近餐館的呢?苦思冥想了半天,機機想出了個方法:計算所在位置P與北京所有餐館 的距離,然后返回距離<=1000米的餐館。小得意了一會兒,機機發現北京的餐館何其多啊,這樣計算不得了,于是想了,既然知道經緯度了,那它應該 知道自己在西城區,那應該計算所在位置P與西城區所有餐館的距離啊,機機運用了遞歸的思想,想到了西城區也很多餐館啊,應該計算所在位置P與所在街道所有 餐館的距離,這樣計算量又小了,效率也提升了。
機機的計算思想很樸素,就是通過過濾的方法來減小參與計算的餐館數目,從某種角度上講,機機在使用索引技術。
一提到索引,大家腦子里馬上浮現出B樹索引,因為大量的數據庫(如MySQL、oracle、PostgreSQL等)都在使用B樹。B樹索引本質上是對索引字段進行排序,然后通過類似二分查找的方法進行快速查找,即它要求索引的字段是可排序的,一般而言,可排序的是一維字段,比如時間、年齡、薪水等等。但是對于空間上的一個點(二維,包括經度和緯度),如何排序呢?又如何索引呢?解決的方法很多,下文介紹一種方法來解決這一問題。
思想:如果能通過某種方法將二維的點數據轉換成一維的數據,那樣不就可以繼續使用B樹索引了嘛。那這種方法真的存在嘛,答案是肯定的。目前很火的GeoHash算法就是運用了上述思想,下面我們就開始GeoHash之旅吧。
一、感性認識GeoHash
首先來點感性認識,http://openlocation.org/geohash/geohash-js/ 提供了在地圖上顯示geohash編碼的功能。
1)GeoHash將二維的經緯度轉換成字符串,比如下圖展示了北京9個區域的GeoHash字符串,分別是WX4ER,WX4G2、WX4G3等 等,每一個字符串代表了某一矩形區域。也就是說,這個矩形區域內所有的點(經緯度坐標)都共享相同的GeoHash字符串,這樣既可以保護隱私(只表示大 概區域位置而不是具體的點),又比較容易做緩存,比如左上角這個區域內的用戶不斷發送位置信息請求餐館數據,由于這些用戶的GeoHash字符串都是WX4ER,所以可以把WX4ER當作key,把該區域的餐館信息當作value來進行緩存,而如果不使用GeoHash的話,由于區域內的用戶傳來的經緯度是各不相同的,很難做緩存。
2)字符串越長,表示的范圍越精確。如圖所示,5位的編碼能表示10平方千米范圍的矩形區域,而6位編碼能表示更精細的區域(約0.34平方千米)
3)字符串相似的表示距離相近(特殊情況后文闡述),這樣可以利用字符串的前綴匹配來查詢附近的POI信息。如下兩個圖所示,一個在城區,一個在郊 區,城區的GeoHash字符串之間比較相似,郊區的字符串之間也比較相似,而城區和郊區的GeoHash字符串相似程度要低些。
![]() |
![]() |
</tr>
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
城區 | 郊區 | </tr> </tbody> </table>
bit | min | mid | max | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | -90.000 | 0.000 | 90.000 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0 | 0.000 | 45.000 | 90.000 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 0.000 | 22.500 | 45.000 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 22.500 | 33.750 | 45.000 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 33.7500 | 39.375 | 45.000 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0 | 39.375 | 42.188 | 45.000 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0 | 39.375 | 40.7815 | 42.188 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0 | 39.375 | 40.07825 | 40.7815 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 39.375 | 39.726625 | 40.07825 | </tr>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 39.726625 | 39.9024375 | 40.07825 | </tr> </tbody> </table>
bit | min | mid | max | </tr>|||||||||||||||||||||||||
1 | -180 | 0.000 | 180 | </tr>|||||||||||||||||||||||||
1 | 0.000 | 90 | 180 | </tr>|||||||||||||||||||||||||
0 | 90 | 135 | 180 | </tr>|||||||||||||||||||||||||
1 | 90 | 112.5 | 135 | </tr>|||||||||||||||||||||||||
0 | 112.5 | 123.75 | 135 | </tr>|||||||||||||||||||||||||
0 | 112.5 | 118.125 | 123.75 | </tr>|||||||||||||||||||||||||
1 | 112.5 | 115.3125 | 118.125 | </tr>|||||||||||||||||||||||||
0 | 115.3125 | 116.71875 | 118.125 | </tr>|||||||||||||||||||||||||
1 | 115.3125 | 116.015625 | 116.71875 | </tr>|||||||||||||||||||||||||
1 | 116.015625 | 116.3671875 | 116.71875 | </tr> </tbody> </table>