PHP,Mysql-根据一个给定经纬度的点,进行附近地点查询–合理利用算法,效率提高2125倍

目前的工作是需要对用户的一些数据进行分析,每个用户都有若干条记录,每条记录中有用户的一个位置,是用经度和纬度表示的。
还有一个给定的数据库,存储的是一些已知地点以及他们的经纬度,内有43W多条的数据。
现在需要拿用户的经纬度和已知地点进行距离匹配,如果它们之间的距离小于一定的数据,比如说500米,就认为用户是在这个地点。
MYSQL本身是支持空间索引的,但是在5.x的版本中,取消了对Distance()和Related()的支持,参考这里:MySQL 5.1参考手册 :: 19. 中的空间扩展 19.5.6. 测试几何类之间空间关系的函数,无法使用空间的距离函数去直接去查询距离在一定范围内的点。所以,我首先想到的是,对每条记录,去进行遍历,跟数据库中的每一个点进行距离计算,当距离小于500米时,认为匹配。这样做确实能够得到结果,但是效率极其低下,因为每条记录都要去循环匹配40W条数据,其消耗的时间可想而知。经过记录,发现每条记录处理的时间消耗达到1700ms,针对每天上亿的数据量,这样一个处理速度,让人情何以堪啊。。。
我自己也有个想法,就是找到每条记录所在点的经纬度周围的一个大概范围,比方说正方形的四个点,然后使用mysql的空间计算,使用MBR去得出点在这个矩形内的已知记录,然后进行匹配。可惜,自己没想出能计算到四个点经纬度的方法。
意外的,查询到了一个关于这个计算附近地点搜索初探,里面使用python实现了这个想法。
所以参考了一下原文中的算法,使用PHP进行了实现。
实现原理也是很相似的,先算出该点周围的矩形的四个点,然后使用经纬度去直接匹配数据库中的记录。

参考wiki百科上的一些球面计算公式:

假设已知点的经纬度分别为$lng, $lat
先实现经度范围的查询,
在haversin公式中令φ1 = φ2,可得:

用PHP进行计算,就是:

/$lat 已知点的纬度
$dlng =  2 * asin(sin($distance / (2 * EARTH_RADIUS)) / cos(deg2rad($lat)));
$dlng = rad2deg($dlng);//转换弧度

然后是纬度范围的查询,

在haversin公式中令 Δλ = 0,可得

在PHP中进行计算,就是:

$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)

我把以上方法写成了一个函数,综合起来就是:

define(EARTH_RADIUS, 6371);//地球半径,平均半径为6371km
 /**
 *计算某个经纬度的周围某段距离的正方形的四个点
 *
 *@param lng float 经度
 *@param lat float 纬度
 *@param distance float 该点所在圆的半径,该圆与此正方形内切,默认值为0.5千米
 *@return array 正方形的四个点的经纬度坐标
 */
 function returnSquarePoint($lng, $lat,$distance = 0.5){

    $dlng =  2 * asin(sin($distance / (2 * EARTH_RADIUS)) / cos(deg2rad($lat)));
    $dlng = rad2deg($dlng);

    $dlat = $distance/EARTH_RADIUS;
    $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)
                );
 }
//使用此函数计算得到结果后,带入sql查询。
$squares = returnSquarePoint($lng, $lat);
$info_sql = "select id,locateinfo,lat,lng from `lbs_info` 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‘]} ";

在lat和lng上建立一个联合索引后,使用此项查询,每条记录的查询消耗平均为0.8毫秒,相比以前的1700ms,真的是天壤之别啊。效率真真的是以前的2125倍~~

总结:这应该也不是效率最好的办法,但是效率比以前确实有明显的提升。请记住,总有办法更好的。

原文来自:http://digdeeply.org/archives/06152067.html

时间: 2024-08-02 04:38:51

PHP,Mysql-根据一个给定经纬度的点,进行附近地点查询–合理利用算法,效率提高2125倍的相关文章

Java,Mysql-根据一个给定经纬度的点,进行附近500米地点查询–合理利用算法

LBS 球面距离公式 http://wiki.myoa.info/zh-blog:20 最近做一个项目:需要查询一个站点(已知该站点经纬度)500米范围内的其它站点.所以,我首先想到的是,对每条记录,去进行遍历,跟数据库中的每一个点进行距离计算,当距离小于500米时,认为匹配.这样做确实能够得到结果,但是效率极其低下,因为每条记录都要去循环匹配n条数据,其消耗的时间可想而知. 于是我就想到一个先过滤出大概的经纬度范围再进行计算.比方说正方形的四个点,于是我在网上搜索,意外的,查询到了一个关于这个

PHP根据一个给定经纬度的点,进行附近地点查询–合理利用算法

实现原理先算出该点周围的矩形的四个点,然后使用经纬度去直接匹配数据库中的记录.    //获取周围坐标    public function returnSquarePoint($lng, $lat,$distance = 0.5){          $earthRadius = 6378138;         $dlng =  2 * asin(sin($distance / (2 * $earthRadius)) / cos(deg2rad($lat)));         $dlng 

PHP和Mysql如何将给定字段的数组传入SQL进行查询,使用in查询

//$articleIDs是一个存着字段ID的数组 $IDs=implode(',', $articleIDs);//将数组中的每个id转换为以逗号分隔的串,然后就可以使用in查询返回id在这个数组中的所有数据 $sql2="select * from article_group where id in ($IDs)"; $result2=mysqli_query($con,$sql2); while($row=mysqli_fetch_array($result2)){ var_du

给定经纬度定位某个城市

方案1 通过GEO HASH,抓取每个格子里的点的地址信息,然后把这个地址信息作为GEO中点坐标的一个信息,当查询给定经纬度是在哪个城市的时候,查询过程是查询距离这个经纬度最近的带有地址的点,认为给定经纬度和最近的这个点是同一个地址下的.通过不断完善GEO中的每个坐标下的地址信息,可以提高整个系统的精度.这种方法相当于一种间接获取的方法的. 方案2 我们知道每个地区是有一定区域的,可以认为是一个闭合的多边形,定位城市,可以理解为判断某个点,是否在某个区域内. 第一点:如何获取城市的区域边界 很幸

程序员面试100题之十:快速找出一个数组中的两个数字,让这两个数字之和等于一个给定的值(转)

能否快速找出一个数组中的两个数字,让这两个数字之和等于一个给定的值,为了简化起见,我们假设这个数组中肯定存在至少一组符合要求的解. 假如有如下的两个数组,如图所示: 5,6,1,4,7,9,8 给定Sum= 10 1,5,6,7,8,9 给定Sum= 10 分析与解法 这个题目不是很难,也很容易理解.但是要得出高效率的解法,还是需要一番思考的. 解法一 一个直接的解法就是穷举:从数组中任意取出两个数字,计算两者之和是否为给定的数字. 显然其时间复杂度为N(N-1)/2即O(N^2).这个算法很简

JavaScript面试题:重复输出一个给定的字符串

面试题 重复输出一个给定的字符串(str第一个参数)n 次 (num第二个参数),如果第二个参数num不是正数的时候,返回空字符串. function repeatStringNumTimes(str, num) { return str; } repeatStringNumTimes("abc", 3); 提供测试情况: repeatStringNumTimes("*", 3) //应该返回 "***". repeatStringNumTime

写一个函数,对于一个给定的整数,如果它的二进制模式从正向看和反向看是一样的,那么返回true;

写一个函数,对于一个给定的整数,如果它的二进制模式从正向看和反向看是一样的,那么返回true:也就是实现这样一个函数boolean isPalindrome(int x); 分析一下,该题目主要是通过移位来实现,二进制模式从正向看和反向看是一样的,说明这个二进制数两边是对称的, 画个图看看: 代码如下: boolean isPalindrome(int x){ int flag = 1,i,j,temp;    while(1){        if(num&(0x1<<flag)){

全球78707个主要城市信息MYSQL数据库,包含经纬度坐标值

全世界78707个主要城市信息MYSQL数据库,包含经纬度坐标值,已经导出成了SQL文件,因为比较大,有10M,所以这里单独导出来,供升级失败的同学手工导入,如图: 这个数据主要是用于天气预报插件的,天气预报插件,需要首先从这个库里面,查询城市的ID,在把ID传给接口,返回天气预报数据. 这里上传不了附件,下载请去官方博客:http://blog.erdaicms.com/lvyou/97

***mysql 用一个表的一列,去更新另一表的一列

需求: 老板给了一个EXCEL数据,是本人提供的一个模板,含ID,现在相当于要导入这新增的一列数据到数据库中的某一个表. 方法一:用navicat,在excel中复制一列,再粘贴到navicat中的一列中去 方法二:用sql的方法:先建一个临时表,将数据导入,里面有ID和desc两列,再执行下面的语句 UPDATE gy_doctor a, gy_tmp b SET a.dr_desc = b.`desc` WHERE a.dr_id = b.id; 设有表t1: id name1 null2