PHP实现附近的人、按距离排序之MongoDB方案

相关文章:PHP实现附近的人、按距离排序之Redis GEO方案

      前一篇文章介绍了使用Redis GEO实现附近的人功能,但是留下了诸多弊端,因此要换一种方案,使用MongoDB存储用户的位置信息,实现附近的人功能,这里推荐使用这一种方案。

      首先服务器需要安装MongoDB软件以及php的mongo_db扩展,这里不做介绍。然后封装一个php操作的MongoDB的单例类,可以参考文章:PHP封装一个MongoDB单例类

1.更新用户位置信息时存入MongoDB,示例如下:   

 $collname,
            "filter"    => [
                "user_id" =>$user_id
                ]
            ];
            $userMon = Mongo::getInstance()->command($sel);
            $userMon =  $this->json_to_array($userMon);
            if(empty($userMon)){ //插入
                $rows = [
                    ["user_id" => $user_id,"loc"=>["lon"=>round($longitude,6),"lat"=>round($latitude,6)]]
                ];
                Mongo::getInstance()->insert($collname, $rows);
            }else{ //更新
                $updates = [
                    [
                        "q"     => ["user_id" => $user_id],
                        "u"     => ['$set' => ["loc"=>["lon"=>round($longitude,6),"lat"=>round($latitude,6)]]],
                        "multi" => true,
                    ]
                ];
                Mongo::getInstance()->update($collname, $updates);
            }
 
        //省略其它逻辑......
 
    }
    
    //对象转数组
    public function json_to_array($json_str) {
        $json_str1 = $json_str;
        if (is_array($json_str) || is_object($json_str)) {
            $json_str = $json_str1;
        } else if (is_null(json_decode($json_str))) {
            $json_str = $json_str1;
        } else {
            $json_str = strval($json_str);
            $json_str = json_decode($json_str, true);
        }
        $json_arr = array();
        foreach ($json_str as $k => $w) {
            if (is_object($w)) {
                $json_arr[$k] = json_to_array($w); //判断类型是不是object
            } else if (is_array($w)) {
                $json_arr[$k] = json_to_array($w);
            } else {
                $json_arr[$k] = $w;
            }
        }
        return $json_arr;
    }
}

2.给字段添加索引,注意设置uid唯一键,loc字段索引类型为2d

PHP实现附近的人、按距离排序之MongoDB方案_第1张图片

3.封装根据一个用户经纬度坐标,查询MongoDB中周边用户的方法,支持条件、分页:

/**
 * 根据经纬度 查询MongoDB周边用户
 * @param $lon 经度
 * @param $lat 纬度
 * @param $where 查询条件
 * @param $page 页码
 * @param  $limit 每页条数
 * @return $maxDistance 范围 单位米
 */    
public function mongoNearGeo($lon=0, $lat=0, $where = [], $page=1, $limit=10, $maxDistance = 50000){
    $collname = 'user_location';
    $filter = array(
        'loc' => array(
            '$nearSphere' => array($lon, $lat),
            '$maxDistance' => $maxDistance / 6378137
        ),
    );
    if ($where) {
        $filter = array_merge($filter, $where);
    }
    $page > 1 ? $skip = ($page - 1) * $limit : $skip = 0;
    $options = [
        'limit' => $limit,
        'skip' => $skip
    ];
    $res = Mongo::getInstance()->query($collname, $filter, $options);
    $res =  $this->json_to_array($res);
    if(!empty($res) && is_array($res)){
        foreach ($res as $k => $v){
            //计算距离
            $res[$k]['distance'] = $this->distance($lat, $lon, $v['loc']['lat'], $v['loc']['lon']);
        }
    }
    return $res;
}

/**
 *计算地球表面2点之间的球面距离,返回米数
 * @param $lat1
 * @param $lon1
 * @param $lat2
 * @param $lon2
 * @param  $radius
 * @return float
 */
function distance($lat1, $lon1, $lat2, $lon2, $radius = 6378140)
{
    $rad = floatval(M_PI / 180.0);
    $lat1 = floatval($lat1) * $rad;
    $lon1 = floatval($lon1) * $rad;
    $lat2 = floatval($lat2) * $rad;
    $lon2 = floatval($lon2) * $rad;
    $theta = $lon2 - $lon1;
    $dist = acos(sin($lat1) * sin($lat2) +
        cos($lat1) * cos($lat2) * cos($theta)
    );
    if ($dist < 0) {
        $dist += M_PI;
    }
    $dist = ceil($dist * $radius);
    return $dist;
}

      到此实现了使用MongoDB做附近的人功能,还可以把用户的性别,年龄等存进去,因为MongoDB支持条件查询,所以相对于Redis GEO方案更便于扩展功能,而且在数据量大的情况下在查询效率和内存占用上,也比Redis GEO方案更优。

 

你可能感兴趣的:(PHP,MongoDB)