目前的工做是须要对用户的一些数据进行分析,每一个用户都有若干条记录,每条记录中有用户的一个位置,是用经度和纬度表示的。
还有一个给定的数据库,存储的是一些已知地点以及他们的经纬度,内有43W多条的数据。
如今须要拿用户的经纬度和已知地点进行距离匹配,若是它们之间的距离小于必定的数据,好比说500米,就认为用户是在这个地点。
MYSQL自己是支持空间索引的,可是在5.x的版本中,取消了对Distance()和Related()的支持,没法使用空间的距离函数去直接去查询距离在必定范围内的点。因此,我首先想到的是,对每条记录,去进行遍历,跟数据库中的每个点进行距离计算,当距离小于500米时,认为匹配。这样作确实可以获得结果,可是效率极其低下,由于每条记录都要去循环匹配40W条数据,其消耗的时间可想而知。通过记录,发现每条记录处理的时间消耗达到1700ms,针对天天上亿的数据量,这样一个处理速度,让人情何以堪啊。。。
我本身也有个想法,就是找到每条记录所在点的经纬度周围的一个大概范围,比方说正方形的四个点,而后使用mysql的空间计算,使用MBR去得出点在这个矩形内的已知记录,而后进行匹配。惋惜,本身没想出能计算到四个点经纬度的方法。php
意外的,查询到了一个关于这个计算附近地点搜索初探,里面使用python实现了这个想法。
因此参考了一下原文中的算法,使用PHP进行了实现。
实现原理也是很类似的,先算出该点周围的矩形的四个点,而后使用经纬度去直接匹配数据库中的记录。html
红色部分为要求的搜索范围,绿色部分咱们能间接获得的结果范围python
参考wiki百科上的一些球面计算公式:mysql
Great-circle distance
Haversine formulagit
假设已知点的经纬度分别为$lng, $lat
先实现经度范围的查询,
在haversin公式中令φ1 = φ2,可得:算法
用PHP进行计算,就是:sql
Example数据库
代码以下 | 复制代码 |
|
而后是纬度范围的查询,spa
在haversin公式中令 Δλ = 0,可得
在PHP中进行计算,就是:
Example
代码以下 | 复制代码 |
$dlat = $distance/EARTH_RADIUS;//EARTH_RADIUS地球半径 |
最后,就能够得出四个点的坐标:
left-top : (lat + dlat, lng – dlng)
right-top : (lat + dlat, lng + dlng)
left-bottom : (lat – dlat, lng – dlng)
right-bottom: (lat – dlat, lng + dlng)
我把以上方法写成了一个函数,综合起来就是:
Example
代码以下 | 复制代码 |
define(EARTH_RADIUS,6378.137);//地球半径,平均半径为6378.137km /** $squares = returnSquarePoint($lng, $lat); |
//计算两个坐标的直线距离 public function getDistance($lat1, $lng1, $lat2, $lng2){ $earthRadius =6378.137; //近似地球半径米 // 转换为弧度 $lat1 = ($lat1 * pi()) / 180; $lng1 = ($lng1 * pi()) / 180; $lat2 = ($lat2 * pi()) / 180; $lng2 = ($lng2 * pi()) / 180; // 使用半正矢公式 用尺规来计算 $calcLongitude = $lng2 - $lng1; $calcLatitude = $lat2 - $lat1; $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2); $stepTwo = 2 * asin(min(1, sqrt($stepOne))); $calculatedDistance = $earthRadius * $stepTwo; return round($calculatedDistance); }
在lat和lng上创建一个联合索引后,使用此项查询,每条记录的查询消耗平均为0.8毫秒,相比之前的1700ms,真的是天壤之别啊。效率真真的是之前的2125倍~~
总结:这应该也不是效率最好的办法,可是效率比之前确实有明显的提高。请记住,总有办法更好的。