优化附近的人,根据经纬度计算距离离我最近的人(数据量大)

  •   
  • 7114
  • 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&#91;$key&#93;&#91;'distance'&#93; = round($distance/1000,2)."km";
				}else{							//102km
					$arr&#91;$key&#93;&#91;'distance'&#93; = ceil($distance/1000)."km";
				}
			}else{								//385m
				$arr&#91;$key&#93;&#91;'distance'&#93; = $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);
}

QQ图片20151209114236

你可以根据你的用户数据进行颗粒度细化。根据你的需求进行优化你的数据吧。

有兴趣的人会redis的也可以研究研究 http://redisdoc.com/geo/georadius.html

很多人就像山川,像河流。山川不曾回应,河流一去不返,却依然在你生命里,波澜壮阔。