优化附近的人,根据经纬度计算距离离我最近的人(数据量大)
- 7850
- MySQL
- 7
- super_dodo
- 2015/12/09
在很多的APP或者移动互联网的应用中,地图经纬度和用户地理位置以及附近的人常常吸引到用户的使用。
如果是全国的用户,这个时候又恰巧需要按照由近到远的分页展示。已知我的经纬度(可能是实时定位获取的),也知道其他用户(可能是商家)的经纬度.
常规思路:取出所有数据的经纬度,进行计算距离,并php数组排序截取分页。但是问题发生了,当数据量很大的时候,就会存在取数据很多,计算两点间的距离也很耗时,这样就会存在性能的瓶颈。会导致cpu和内存负荷过高,甚至服务器宕机等。
下面是优化后的思路:根据我的经纬度加减一定的经纬度,在数据库里面取出来,如果没取到数据就继续扩大范围,直到取到数据。对于这个相应的参数和扩大的范围根据你的需要去设置。我在此处设置为,第一次经纬度绝对值为1,5,10,50,100,200(其实可以不用到200,经度为正负180,纬度为正负90,如果你是国内应用的话,这个更加可以细化,请自己去百度地图上取值。)
下面直接上代码(有些粗糙,欢迎各位进行优化,需要的话可以写个递归)此处使用ThinkPHP框架
//获取附近的人 public function nearbyFriends(){ header('Content-Type:text/html;Charset=UTF-8'); //字符编码 $lat = I("lat"); //纬度 $lng = I("lng"); //经度 $page = I("page") ? I("page") : 1; $rows = 20; $where = " state=1 "; //状态正常的朋友 if($lat && $lng){ //有经纬度 $lat1 = $lat + 1; $lat_1 = $lat - 1; $lng1 = $lng + 1; $lng_1 = $lng - 1; $where1 = $where . " AND lat>'{$lat_1}' AND lat<'{$lat1}' AND lng>'{$lng_1}' AND lng<'{$lng1}' "; //附近经纬度绝对值1 $askCnt = M("member_list")->where($where1)->count("id"); //统计未读消息数 if($askCnt < 500 ){ $lat1 = $lat + 5; $lat_1 = $lat - 5; $lng1 = $lng + 5; $lng_1 = $lng - 5; $where5 = $where . " AND lat>'{$lat_1}' AND lat<'{$lat1}' AND lng>'{$lng_1}' AND lng<'{$lng1}' "; //附近经纬度绝对值1 $askCnt = M("member_list")->where($where5)->count("id"); //统计未读消息数 if($askCnt < 500 ){ $lat1 = $lat + 10; $lat_1 = $lat - 10; $lng1 = $lng + 10; $lng_1 = $lng - 10; $where10 = $where . " AND lat>'{$lat_1}' AND lat<'{$lat1}' AND lng>'{$lng_1}' AND lng<'{$lng1}' "; //附近经纬度绝对值1 $askCnt = M("member_list")->where($where10)->count("id"); //统计未读消息数 if($askCnt < 500 ){ $lat1 = $lat + 50; $lat_1 = $lat - 50; $lng1 = $lng + 50; $lng_1 = $lng - 50; $where50 = $where . " AND lat>'{$lat_1}' AND lat<'{$lat1}' AND lng>'{$lng_1}' AND lng<'{$lng1}' "; //附近经纬度绝对值1 $askCnt = M("member_list")->where($where50)->count("id"); //统计未读消息数 if($askCnt < 500 ){ $lat1 = $lat + 100; $lat_1 = $lat - 100; $lng1 = $lng + 100; $lng_1 = $lng - 100; $where100 = $where . " AND lat>'{$lat_1}' AND lat<'{$lat1}' AND lng>'{$lng_1}' AND lng<'{$lng1}' "; //附近经纬度绝对值1 $askCnt = M("member_list")->where($where100)->count("id"); //统计未读消息数 } } } } } if($askCnt){ //取到500条数据---根据经纬度 $gap_where = $where . " AND lat>'{$lat_1}' AND lat<'{$lat1}' AND lng>'{$lng_1}' AND lng<'{$lng1}' "; //附近经纬度绝对值1 }else{ //根据经纬度没取到数据 $gap_where = $where; } $offset = floor($page / 25); //每次取500条 $dbPageSize = ($offset+1) * 500; //取500条进行计算排序 $arr = M("member_list")->where($gap_where)->limit("{$offset},{$dbPageSize}")->select(); //echo M("member_list")->_sql(); if($arr){ foreach ($arr as $key => $value) { if($lat != 0 && $lng != 0 && $value['lat'] && $value['lng']){ $my_map = $lat.','.$lng; $vip_map = $value['lat'].','.$value['lng']; $distance = $this->getDistance($my_map,$vip_map); }else{ $distance = mt_rand(100,2000); //给个随机的距离 } //排序的arr $arrSort[$value['id']] = $distance; $arr[$key]['distance'] = $distance; if($distance >= 1000){ if($distance < 1000000){ //8.88km $arr[$key]['distance'] = round($distance/1000,2)."km"; }else{ //102km $arr[$key]['distance'] = ceil($distance/1000)."km"; } }else{ //385m $arr[$key]['distance'] = $distance."m"; } } asort($arrSort); $newArr = array(); //所有的 foreach($arrSort as $kk => $vv){ foreach($arr as $kkk =>$vvv){ if($kk == $vvv['id']){ $newArr[] = $vvv; } } } $start = ($page-1)*$rows; //计算每次分页的开始位置 $total = count($newArr); //总的记录数 $countPage = ceil($total/$rows); //计算总页面数 $pageData = array(); $pageData = array_slice($newArr,$start,$rows); // 最后一个值为true 保留原来键 $data_list["firends"] = $pageData; $this->result_print(1,"获取附近的人成功!",$data_list); }else{ $this->result_print(0,"未找到附近的人!"); } }
附上计算两点间距离的方法
//求两者经纬度之间的距离-- my_map 我的经纬度---map 商家经纬度 public function getDistanceNew($my_map,$map){ if(!strstr($my_map,',')){ return '未知'; } //找到分号再进行 if(!strstr($map,',')){ return '未知'; } //找到分号再进行 $myMap = explode(',',$my_map); $mapArr = explode(',',$map); $lat1 = $myMap['1']; $lng1 = $myMap['0']; $lat2 = $mapArr['1']; $lng2 = $mapArr['0']; //将角度转为狐度 $radLat1 = deg2rad($lat1);//deg2rad()函数将角度转换为弧度 $radLat2 = deg2rad($lat2); $radLng1 = deg2rad($lng1); $radLng2 = deg2rad($lng2); $a = $radLat1 - $radLat2; $b = $radLng1 - $radLng2; $s = 2*asin(sqrt(pow(sin($a/2),2)+cos($radLat1)*cos($radLat2)*pow(sin($b/2),2)))*6378.137*1000; return round($s); }
你可以根据你的用户数据进行颗粒度细化。根据你的需求进行优化你的数据吧。
有兴趣的人会redis的也可以研究研究 http://redisdoc.com/geo/georadius.html
很多人就像山川,像河流。山川不曾回应,河流一去不返,却依然在你生命里,波澜壮阔。
相关阅读
- 通过Google API客户端访问Google Play帐户报告PHP库
- PHP执行文件的压缩和解压缩方法
- 消息中间件MQ与RabbitMQ面试题
- 如何搭建一个拖垮公司的技术架构?
- Yii2中ElasticSearch的使用示例
热门文章
- 通过Google API客户端访问Google Play帐户报告PHP库
- PHP执行文件的压缩和解压缩方法
- 消息中间件MQ与RabbitMQ面试题
- 如何搭建一个拖垮公司的技术架构?
- Yii2中ElasticSearch的使用示例
最新文章
- 通过Google API客户端访问Google Play帐户报告PHP库
- PHP执行文件的压缩和解压缩方法
- 消息中间件MQ与RabbitMQ面试题
- 如何搭建一个拖垮公司的技术架构?
- Yii2中ElasticSearch的使用示例