隔着屏幕轻易产生感情的你,肯定很孤独吧。

这几天做新功能的时候遇到了一个需求是需要按照企业的好评信誉度和距离给用户推荐,简单说就是根据表里已有的企业经纬度和用户实时定位的经纬度做计算然后按照好评信誉度筛选。这个计算距离却是很麻烦,百度了很多,基本上都是复制粘贴的,也没几句注释,让人很难使用,自己研究了下,整理了一个写在这里。
首先这个计算经纬度是需要有很强的数学底蕴,不然就只能去复制别人的代码了,推荐使用以下代码进行计算
假设数据库表中原有的经纬度字段为latitude和longitude,传入的经纬度字段则为lat和long


ROUND(
   6378.138 * 2 * ASIN(
      SQRT(
        POW(
          SIN(
                (
                  lat * PI() / 180 - latitude * PI() / 180
                ) / 2
             ),
               2
             ) + COS(lat * PI() / 180) * COS(latitude * PI() / 180) * POW(
                    SIN(
                        (
                            long * PI() / 180 - longitude * PI() / 180
                        ) / 2
                    ),
                    2
                )
            )
   )

举个例子:
使用TP的PHP代码写法为:


    $input=input('get.');
    $data=db('staff')->alias('s')->join('company c','s.staff_company=c.com_id','left')
		->where("ROUND(
                     6378.138 * 2 * ASIN(
                       SQRT(
                        POW(
                          SIN(
                          (
                             {$input['latitude']} * PI() / 180 - c.com_latitude * PI() / 180
                          ) / 2
                        ),
                       2
                    ) + COS({$input['latitude']} * PI() / 180) * COS(c.com_latitude * PI() / 180) * POW(
                      SIN(
                        (
                            {$input['longitude']} * PI() / 180 - c.com_longitude * PI() / 180
                        ) / 2
                      ),
                     2
                  )
              )
          )  )")
			->limit(5)
			->order('s.staff_score desc')->select(false);
		dump($data);

打印出来的sql语句为:


SELECT * FROM `qb_staff` `s` LEFT JOIN `qb_company` `c` ON `s`.`staff_company`=`c`.`com_id` WHERE  (  ROUND(
        6378.138 * 2 * ASIN(
            SQRT(
                POW(
                    SIN(
                        (
                            35.09 * PI() / 180 - c.com_latitude * PI() / 180
                        ) / 2
                    ),
                    2
                ) + COS(35.09 * PI() / 180) * COS(c.com_latitude * PI() / 180) * POW(
                    SIN(
                        (
                            109.11 * PI() / 180 - c.com_longitude * PI() / 180
                        ) / 2
                    ),
                    2
                )
            )
        ) ) ) ORDER BY s.staff_score desc LIMIT 5

另外建议先限制一下经纬度的查询范围,比如增加这四个where条件:


latitude > '.$lat.'-1 and latitude < '.$lat.'+1 and longitude > '.$lon.'-1 and longitude < '.$lon.'+1

只对于经度和纬度大于或小于该用户1度(111公里)范围内的用户进行距离计算,同时将经纬度字段加入索引,这样在面对数据极多的时候查询速度会很快

更新:

优化上述方法

    public static $lngPerDeg  = 89.39;//经度1° 89.39KM
    public static $latPerDeg  = 111;//纬度1° 111KM
    public static $basePerDeg = 0.011;//1km转换为1纬度

/**
 * @param $lng
 * @param $lat
 * @param string $f_lng
 * @param string $f_lat
 * @param string $as
 * @param string $db
 * @return string
 * 按经纬度查询 标准SQL
 */
public static function zoneSql($lng,$lat,$f_lng='lng',$f_lat='lat', $as='dis',$db='') {
    $lngPerDeg = UsersMainExt::$lngPerDeg;
    $latPerDeg = UsersMainExt::$latPerDeg;
    $f_lng     = empty($db) ? "`{$f_lng}`" : "`{$db}`.`{$f_lng}`";
    $f_lat     = empty($db) ? "`{$f_lat}`" : "`{$db}`.`{$f_lat}`";
    $sql       = " sqrt (POW((({$lng}-{$f_lng}) * {$lngPerDeg}),2) + POW((({$lat}-{$f_lat})) * {$latPerDeg},2) ) AS `{$as}` ";

    return $sql;
}
/**
 * @param $lng1
 * @param $lat1
 * @param $lng2
 * @param $lat2
 * @return string
 * 计算经纬度之间距离
 */
public function getDistanceByLocation($lng1,$lat1,$lng2,$lat2){
    $lngPerDeg = UsersMainExt::$lngPerDeg;
    $latPerDeg = UsersMainExt::$latPerDeg;
    $lng1      = sprintf('%.6f',$lng1);
    $lat1      = sprintf('%.6f',$lat1);
    $lng2      = sprintf('%.6f',$lng2);
    $lat2      = sprintf('%.6f',$lat2);
    $dis       = pow(($lng1-$lng2) * $lngPerDeg,2) + pow(($lat1-$lat2) * $latPerDeg,2);
    $dis       = sqrt($dis);

    return     sprintf('%.6f',$dis);
}
分类: MySQL

0 条评论

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据