幾個星期以前的一個項目,需求是根據當前用戶上傳的經緯度坐標,在數據庫幾十萬萬條數據中查詢出符合“周圍3公里范圍內”條件的坐標點。
Mysql本身是支持空間索引的,但是在5.X版本中取消了Distance()和Related(),無法使用空間的距離函數去直接查詢距離在一定范圍內的點。所以,我首先想到的是,對每條數據去進行遍歷,跟數據庫中的每個點進行距離計算,當距離小於3公里時候,認為匹配成功。經測試,這樣做確實能得到結果,但是效率極其低下,因為每條數據都得去和數據庫中的幾十萬條數據進行比對,其耗費的時間可想而知。對於這種情況,是用戶所無法忍受的。
后來經過自己的仔細考慮,以及查詢各種資料,想到一種利用正方形將方圓3公里這個圓包圍起來。利用正方形的四個點,去和用戶上傳的經緯度進行比較。由此,問題轉向了,如何計算正方形四個點經緯度的問題!
無意中看到一個附近地點搜索初探的帖子,里面使用python實現了計算四個點經緯度的方法。由此,我將其用PHP的方式實現了。
其實現原理也是很相似的,先計算出當前點周圍的正方形的四個點,然后使用經緯度直接去數據庫匹配數據。

假設已知點的經緯度分別為$lng, $lat
先實現經度范圍的查詢,
在haversin公式中令φ1 = φ2,可得:

用PHP實現的整體方式,就是:
1 /** 2 * 計算某個經緯度的周圍某段距離的正方形的四個點 3 * 4 * @param 5 * radius 地球半徑 平均6371km 6 * @param 7 * lng float 經度 8 * @param 9 * lat float 緯度 10 * @param 11 * distance float 該點所在圓的半徑,該圓與此正方形內切,默認值為1千米 12 * @return array 正方形的四個點的經緯度坐標 13 */ 14 public function returnSquarePoint($lng, $lat, $distance = 1, $radius = 6371) 15 { 16 $dlng = 2 * asin(sin($distance / (2 * $radius)) / cos(deg2rad($lat))); 17 $dlng = rad2deg($dlng); 18 19 $dlat = $distance / $radius; 20 $dlat = rad2deg($dlat); 21 22 return array( 23 'left-top' => array( 24 'lat' => $lat + $dlat, 25 'lng' => $lng - $dlng 26 ), 27 'right-top' => array( 28 'lat' => $lat + $dlat, 29 'lng' => $lng + $dlng 30 ), 31 'left-bottom' => array( 32 'lat' => $lat - $dlat, 33 'lng' => $lng - $dlng 34 ), 35 'right-bottom' => array( 36 'lat' => $lat - $dlat, 37 'lng' => $lng + $dlng 38 ) 39 ); 40 }
匹配路線時候就可以采取一下辦法(截取當時寫的方法,大家理解就好)
$array[0]就是用戶上傳的起點終點坐標數組
1 $start = $this->returnSquarePoint($array[0]['start_lng'], $array[0]['start_lat']);
下面是匹配方法,只是代碼截取,請諒解! 2 ->andwhere([ 3 '>', 4 'start_lat', 5 $start['right-bottom']['lat'] 6 ]) 7 ->andWhere([ 8 '<', 9 'start_lat', 10 11 $start['left-top']['lat'] 12 ]) 13 ->andWhere([ 14 '>', 15 'start_lng', 16 $start['left-top']['lng'] 17 ]) 18 ->andWhere([ 19 '<', 20 'start_lng', 21 $start['right-bottom']['lng'] 22 ]);
在lat和lng上建立一個聯合索引后,使用此項查詢,運行效率飛漲。
總結:這應該也不是效率最好的辦法,但是效率比以前確實有明顯的提升。大家如果有什么剛好的解決辦法,歡迎留言學習。
