幾個地理位置信息處理方案的對比和分析

對於任何LBS應用來講,讓用戶尋找周圍的好友可能都是一個必不可少的功能,咱們就以這個功能爲例,來看看各類處理方案之間的差別和區別。算法

咱們假設有以下功能需求:數據庫

顯示我附近的人 由近到遠排序 顯示距離 2. 可能的技術方案 排除一些不通用和難以實現的技術,咱們羅列出如下幾種方案:緩存

基於MySQL數據庫 採用GeoHash索引,基於MySQL MySQL空間存儲(MySQL Spatial Extensions) 使用MongoDB存儲地理位置信息 咱們一個個來分析這幾種方案。函數

方案1:基於MySQL數據庫 MySQL的使用很是簡單。對於大部分已經使用MySQL的網站來講,使用這種方案沒有任何遷移和部署成本。性能

而在MySQL中查詢「最近的人」也僅需一條SQL便可,優化

SELECT id, ( 6371 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians ( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM places HAVING distance < 25 ORDER BY distance LIMIT 0 , 100; 注:這條SQL查詢的是在lat,lng這個座標附近的目標,而且按距離正序排列,SQL中的distance單位爲千米。網站

但使用SQL語句進行查詢的缺點也顯而易見,每條SQL的計算量都會很是大,性能將會是嚴重的問題。編碼

先別放棄,咱們嘗試對這條SQL作一些優化。對象

能夠將圓形區域抽象爲正方形,以下圖排序

根據維基百科上的球面計算公式,能夠根據圓心座標計算出正方形四個點的座標。

而後,查詢這個正方形內的目標點。

SQL語句能夠簡化以下:

SELECT * FROM places WHERE ((lat BETWEEN ? AND ?) AND (lng BETWEEN ? AND ?)) 這樣優化後,雖然數據不徹底精確,但性能提高很明顯,而且能夠經過給lat lng字段作索引的方式進一步加快這條SQL的查詢速度。對精度有要求的應用也能夠在這個結果上再進行計算,排除那些在方塊範圍內但不在原型範圍內的數據,已達到對精度的要求。

但是這樣查詢出來的結果,是沒有排序的,除非再進行一些SQL計算。但那又會在查詢的過程當中產生臨時表排序,可能會形成性能問題。

方案2:GeoHash索引,基於MySQL GeoHash是一種地址編碼,經過切分地圖區域爲小方塊(切分次數越多,精度越高),它能把二維的經緯度編碼成一維的字符串。也就是說,理論上geohash字符串表示的並非一個點,而是一個矩形區域,只要矩形區域足夠小,達到所需精度便可。(其實MongoDB的索引也是基於geohash)

如:wtw3ued9m就是目前我所在的位置,下降一些精度,就會是wtw3ued,再下降一些精度,就會是wtw3u。(點擊連接查看座標編碼對應Google地圖的位置)

因此這樣一來,咱們就能夠在MySQL中用LIKE ‘wtw3u%’來限定區域範圍查詢目標點,而且能夠對結果集作緩存。更不會由於微小的經緯度變化而沒法用上數據庫的Query Cache。

這種方案的優勢顯而易見,僅用一個字符串保存經緯度信息,而且精度由字符串從頭至尾的長度決定,能夠方便索引。

但這種方案的缺點是:從geohash的編碼算法中能夠看出,靠近每一個方塊邊界兩側的點雖然十分接近,但所屬的編碼會徹底不一樣。實際應用中,雖然能夠經過去搜索環繞當前方塊周圍的8個方塊來解決該問題,但一會兒將原來只須要1次SQL查詢變成了須要查詢9次,這樣不只增大了查詢量,也將本來簡單的方案複雜化了。

除此以外,這個方案也沒法直接獲得距離,須要程序協助進行後續的排序計算。

方案3:MySQL空間存儲 MySQL的空間擴展(MySQL Spatial Extensions),它容許在MySQL中直接處理、保存和分析地理位置相關的信息,看起來這是使用MySQL處理地理位置信息的「官方解決方案」。但偏偏很惋惜的是:它卻不支持某些最基本的地理位置操做,好比查詢在半徑範圍內的全部數據。它甚至連兩座標點之間的距離計算方法都沒有(MySQL Spatial的distance方法在5.*版本中不支持)

官方指南的作法是這樣的:

GLength(LineStringFromWKB(LineString(point1, point2))) 這條語句的處理邏輯是先經過兩個點產生一個LineString的類型的數據,而後調用GLength獲得這個LineString的實際長度。

這麼作雖然有些複雜,貌似也解決了距離計算的問題,但讀者須要注意的是:這種方法計算的是歐式空間的距離,簡單來講,它給出的結果是兩個點在三維空間中的直線距離,不是飛機在地球上飛的那條軌跡,而是筆直穿過地球的那條直線。

因此若是你的地理位置信息是用經緯度進行存儲的,你就沒法簡單的直接使用這種方式進行距離計算。

方案4:使用MongoDB存儲地理位置信息 MongoDB原生支持地理位置索引,能夠直接用於位置距離計算和查詢。

另外,它也是現在最流行的NoSQL數據庫之一,除了可以很好地支持地理位置計算以外,還擁有諸如面向集合存儲、模式自由、高性能、支持複雜查詢、支持徹底索引等等特性。

對於咱們的需求,在MongoDB只需一個命令便可獲得所須要的結果:

db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], num:100 }) 查詢結果默認將會由近到遠排序,並且查詢結果也包含目標點對象、距離目標點的距離等信息。

因爲geoNear是MongoDB原生支持的查詢函數,因此性能上也作到了高度的優化,徹底能夠應付生產環境的壓力。

方案總結 基於MongoDB作附近查詢是很方便的一件事情。

MongoDB在地理位置信息方面的表現遠遠不限於此,它還支持更多更加方便的功能,如範圍查詢、距離自動計算等。

相關文章
相關標籤/搜索