前言
當前大多數app都有查找附近的功能, 簡單的有查找周圍的運動場館, 復雜的有滴滴, 摩拜查找周圍的車輛. 本文主要闡述查找附近地點的一般實現.
方案比較
方案1 (性能還不錯)
數據庫直接存經緯度, 然后計算矩形邊界值, 走索引查詢
方案2 (還沒試過)
將經緯度轉換成 一個值, 然后進行比較查詢 genhash
http://blog.csdn.net/newjueqi/article/details/18989867
方案3 (據說高性能, 性能怎樣?待測試)
mongodb 地理類型, 高性能 http://www.tuicool.com/articles/Jfu6fy
sqlserver 地理數據 geography https://msdn.microsoft.com/en-us/library/ff929109.aspx
方案1的實現(本文主要闡述此方案)
實現環境: java+MySQL
場景模擬: 張三用戶在成都天府五街查詢周圍10公里內的地點
1. 首先建立經緯度數據, 比如常見地點的經緯度數據庫, 我這里是網上下載的一個shop_area 表數據,里面包含了一些常見地點的經緯度 ,如下圖
2. 然后根據張三用戶所在的經緯度, 以及他要查詢的距離10公里, 得到查詢范圍矩形的四個頂點, 如下圖:
計算這四個點的 mybatis sql:
<select id="getCurrentLocationRectangle" parameterType="LocationFilter" resultType="LocationFilter">
SELECT
#{myLongitude} - #{distance} / ABS(COS(RADIANS(#{myLatitude})) * 69) AS LongitudeMin, #{myLongitude} + #{distance} / ABS(COS(RADIANS(#{myLatitude})) * 69) AS LongitudeMax, #{myLatitude} - (#{distance} / 69) AS LatitudeMin, #{myLatitude} + (#{distance} / 69) AS LatitudeMax </select>
3. 將剛才得到的矩形四個點的值代入如下sql 來進行經緯度查詢過濾, 記得經緯度字段要建立索引
mybatis sql:
<select id="getUserNearbyAreaList" parameterType="com.anuo.app.modules.coach.entity.CoachFilter" resultType="ShopArea"> SELECT * FROM ( SELECT a.*, GetDistance(#{myLatitude}, #{myLongitude}, a.lat, a.lng) AS distance FROM shop_area a WHERE a.lat BETWEEN #{latitudeMin} AND #{latitudeMax} AND a.lng BETWEEN #{longitudeMin} AND #{longitudeMax} ) z <where> <if test="distance > 0 "> AND z.distance < #{distance} </if> </where> ORDER BY z.distance LIMIT #{pageStart},#{pageSize} </select>
因為先是通過四個點走索引過濾的經緯度數據, 所以大大提升了效率. 並且將我們想要的10公里范圍內的經緯度數據過濾了出來, 雖然多查詢了點數據(見下圖四個叉叉處), 見下面第四步, 將多余的剔除掉
4.上面sql中的 AND z.distance < #{distance} 即 AND z.distance 小於指定距離 #{distance}, 是將下圖畫叉叉部分的經緯度數據剔除,這些數據是多余的, 因為為我們要查詢的是圓圈內的數據
這里用到了一個MySQL GetDistance 函數, 代碼如下
DELIMITER $$
USE `anuoapp`$$ DROP FUNCTION IF EXISTS `GetDistance`$$ CREATE DEFINER=`root`@`localhost` FUNCTION `GetDistance`( myLatitude DECIMAL(11,8),#我當前位置的緯度 myLongitude DECIMAL(11,8),#我當前位置的經度 latitude DECIMAL(11,8), Longitude DECIMAL(11,8) ) RETURNS DOUBLE BEGIN RETURN ( 6371 * ACOS( COS(RADIANS(myLatitude)) * COS(RADIANS(latitude)) * COS(RADIANS(Longitude) - RADIANS(myLongitude)) + SIN(RADIANS(myLatitude)) * SIN(RADIANS(latitude)) ) ); END$$ DELIMITER ;
最后查詢出張三在成都天府五街周圍10公里內的地點
請求url: http://localhost:8080/v1/apiGetUserNearbyArea
請求體:
{
"Token":"6850d1c361e9478ca1e94496ec6b27f9", "Version": "1.8.0", "Entities": [ { "myLatitude":30.54286, "myLongitude":104.075569, "distance":10, "pageSize":10, "pageNumber":1 } ], "IsMobile": true, "PageIndex": 1, "IsInnerTest": true, "IsGetIp": false, "PageSize": 38, "IsEncrypt": true, "Parameters": {} }
響應:
{
"success": true, "totalRow": 11, "entities": [ { "id": "510122004", "areaname": "中和街道", "parentid": 510122, "shortname": "中和街道", "lng": "104.082375", "lat": "30.559141", "level": true, "position": "tr_0 tr_510000 tr_510100 tr_510122", "sort": 25, "distance": 1.9241037391984028 }, { "id": "510107063", "areaname": "石羊場街道", "parentid": 510107, "shortname": "石羊場街道", "lng": "104.048271", "lat": "30.590687", "level": true, "position": "tr_0 tr_510000 tr_510100 tr_510107", "sort": 12, "distance": 5.925643914100619 }, { "id": "510122122", "areaname": "萬安鎮", "parentid": 510122, "shortname": "萬安鎮", "lng": "104.112701", "lat": "30.487444", "level": true, "position": "tr_0 tr_510000 tr_510100 tr_510122", "sort": 18, "distance": 7.114938271111233 }, { "id": "510122120", "areaname": "新興鎮", "parentid": 510122, "shortname": "新興鎮", "lng": "104.149757", "lat": "30.52656", "level": true, "position": "tr_0 tr_510000 tr_510100 tr_510122", "sort": 21, "distance": 7.332851650201873 }, { "id": "510107007", "areaname": "火車南站街道", "parentid": 510107, "shortname": "火車南站街道", "lng": "104.082924", "lat": "30.619801", "level": true, "position": "tr_0 tr_510000 tr_510100 tr_510107", "sort": 7, "distance": 8.5843717771867 } ] }
完整源碼見:
https://gitee.com/anuo/anuoapp
在此項目中搜索 apiGetUserNearbyArea 接口即可定位
完整數據庫見:
https://pan.baidu.com/s/1o9lUJMU