最近接触一个项目,需要根据经纬度计算附近的点。数据量为20w。
如果通过foreach循环MySQL数据库中经纬度,然后再结算距离,速度和性能都会很慢。学习了网上一个方法,特记录下来:
实现原理
算出当前经纬度周围的矩形的四个点,然后使用经纬度去直接匹配数据库中的记录。
红色部分为要求的搜索范围,绿色部分我们能间接得到的结果范围。
参考球面的计算公式
假设已知点的经纬度分别为$lng, $lat
先实现经度范围的查询,
在haversin公式中令φ1 = φ2,可得:
PHP计算方法如下:
//$lat 已知点的纬度 $dlng = 2 * asin(sin($distance / (2 * EARTH_RADIUS)) / cos(deg2rad($lat))); $dlng = rad2deg($dlng);//转换弧度
纬度范围的查询,
在haversin公式中令 Δλ = 0,可得
$dlat = $distance/EARTH_RADIUS;//EARTH_RADIUS地球半径 $dlat = rad2deg($dlat); //转换弧度
最后,就可以得出四个点的坐标:
left-top : (lat + dlat, lng – dlng)
right-top : (lat + dlat, lng + dlng)
left-bottom : (lat – dlat, lng – dlng)
right-bottom: (lat – dlat, lng + dlng)
综合以上步骤,得出具体的方法:
function square_point($lng, $lat,$distance = 0.5){ $earthRadius = 6371; //地球半径,平均半径为6371km $dlng = 2 * asin(sin($distance / (2 * $earthRadius)) / cos(deg2rad($lat))); $dlng = rad2deg($dlng); $dlat = $distance/$earthRadius; $dlat = rad2deg($dlat); return array( 'left-top'=>array('lat'=>$lat + $dlat,'lng'=>$lng-$dlng), 'right-top'=>array('lat'=>$lat + $dlat, 'lng'=>$lng + $dlng), 'left-bottom'=>array('lat'=>$lat - $dlat, 'lng'=>$lng - $dlng), 'right-bottom'=>array('lat'=>$lat - $dlat, 'lng'=>$lng + $dlng) ); } $squares = square_point($lng, $lat); $info_sql = "select id,lat,lng from `location` where lat<>0 and lat>{$squares['right-bottom']['lat']} and lat<{$squares['left-top']['lat']} and lng>{$squares['left-top']['lng']} and lng<{$squares['right-bottom']['lng']} ";
以上方法就可以实现查询附近地点的功能了。别忘记添加 经纬度的混合索引。经过计算20w数据几毫秒就可以查询到了。
存在的问题和方法:
1、没办法排序。按照距离排序。
1)、查询出来的结果在计算距离,然后返回。
2)、直接在SQL语句中添加排序。
$info_sql = "select id,lat,lng from `location` where lat<>0 and lat>{$squares['right-bottom']['lat']} and lat<{$squares['left-top']['lat']} and lng>{$squares['left-top']['lng']} and lng<{$squares['right-bottom']['lng']} order by ABS(lat-当前纬度), ABS(leg-当前经度)" ;
使用百度地图测试,部分Javascript代码如下:
展示效果如下:
好吧 我做梦吧