肉饼铺子 2018-02-11
前言
当前大多数app都有查找附近的功能, 简单的有查找周围的运动场馆, 复杂的有滴滴, 摩拜查找周围的车辆. 本文主要阐述查找附近地点的一般实现. 搜索附近的人也是同样的思路.
方案比较
方案1 (性能还不错)
数据库直接存经纬度, 然后计算矩形边界值, 走索引查询
方案2 (还没试过)
将经纬度转换成 一个值, 然后进行比较查询 genhash
http://blog.csdn.net/newjueqi/article/details/18989867
方案3 (据说高性能, 性能怎样?待测试)
mongodb 地理类型, 高性能http://www.tuicool.com/articles/Jfu6fy
sqlserver 地理数据 geographyhttps://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}, 是将下图画叉叉部分的经纬度数据剔除,这些数据是多余的, 因为为我们要查询的是圆圈内的数据
这里用到了一个MySQLGetDistance 函数, 代码如下
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