通过经纬度计算距离获取附近商家

实际开发中,常常需要获取用户附近的商家,思路是

  • 获取用户位置(经纬度信息)
  • 在数据库中查询在距离范围内的商家

注: 本文章内计算距离所使用地球半径统一为 6378.138 km

public function mpa_list($latitude,$longitude,$distance)
    {
        // $latitude = 34.306465;
        // $longitude = 109.050952;
        // $distance = 5;
        //1.计算最大最小经纬度范围
        $range  = 180 / pi() * $distance / 6378.138; //搜索 N km 之内
        $lngR   = $range / cos($latitude * pi() / 180);
        $maxLat = $latitude + $range; //最大纬度
        $minLat = $latitude - $range; //最小纬度
        $maxLng = $longitude + $lngR; //最大经度
        $minLng = $longitude - $lngR; //最小经度
        //2.查找经纬度符合条件的商家
        $list = Village::select("id","title","longitude","latitude")
                ->whereBetween('latitude', [$minLat, $maxLat])
                ->whereBetween('longitude', [$minLng, $maxLng])
                ->where('status', 1)
                ->get();
        //3.计算距离
        foreach ($list as &$item){
            $item['distance'] = $this->getDistanceBy2Point([$longitude, $latitude], [$item['longitude'], $item['latitude']]);
        }
        if($list){
            $list = $list->toArray();
        }
        //4.排序
        $list = $this->arraySort($list, 'distance');

        return $list;
    }

二维数组排序方法

// 二维数组排序方法
    public static function arraySort($arr, $field, $sort = SORT_ASC){
        $key = array_column($arr, $field);
        array_multisort($key, $sort, $arr);
        return $arr;
    }

根据经纬度计算两点距离

    /**
     * 根据起点坐标和终点坐标测距离
     * @param  [array]   $from     [起点坐标(经纬度),例如:array(118.012951,36.810024)]
     * @param  [array]   $to     [终点坐标(经纬度)]
     * @param  [bool]    $km        是否以公里为单位 false:米 true:公里(千米)
     * @param  [int]     $decimal   精度 保留小数位数
     * @return [string]  距离数值
     */
    public static function getDistanceBy2Point($from, $to, $km = true, $decimal = 2){
        sort($from);
        sort($to);
        $EARTH_RADIUS = 6378.138; // 地球半径系数
        $distance = $EARTH_RADIUS*2*asin(sqrt(pow(sin( ($from[0]*pi()/180-$to[0]*pi()/180)/2),2)+cos($from[0]*pi()/180)*cos($to[0]*pi()/180)* pow(sin( ($from[1]*pi()/180-$to[1]*pi()/180)/2),2)))*1000;
        if($km && $distance > 1000){
            return round($distance / 1000, 2) . 'km';
        }
        return round($distance, $decimal) . 'm';
    }

实际测试:我这边的测试数据比较少,我就用了50公里范围之内的。因为我的数据库里面只添加了连个测试商家,大家将就看一下,理解了就行了。

Image​编辑

本作品采用《CC 协议》,转载必须注明作者和本文链接
Image
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 22

可以尝试使用 redis 的 geo 相关的函数来计算,这类计算可以尝试不放到 php 实现的。

1年前 评论
Image Asuna 1年前

并不太准确,直线距离不等于实际距离,只能当做一个参考

1年前 评论
Image 晏南风 1年前

附近商家,不应该是到达时间最短的嘛?直线距离没啥意义,不计算随便搜索几条同城商家,用户也不会纠结距离真实度。

1年前 评论
Image Get_old (楼主) 1年前
sanders

mysql 本身也有几何计算函数和索引,一旦数据量上来,肯定还是从库里直接排序更合适。

1年前 评论
Image Get_old (楼主) 1年前
Image sanders (作者) 1年前
Image 陈先生 1年前
Image sanders (作者) 1年前
Image 陈先生 1年前
Image sanders (作者) 1年前
Image 陈先生 1年前
Image sanders (作者) 1年前
Image 陈先生 1年前

重庆是座三维城市 :smirk:

1年前 评论

方案太多了。geohash也算一个, 数据库自带的空间索引算一个。

但是,如果是多条件复杂搜索,比如经纬度xxx以内的,性别是女的,年龄在18-30之间的,最近3天上过线的,附加属性xx in (a,b,c)的,这种典型社交场景,数据库就不要想了。必须上es、manticoresearch、zinc这种引擎了。

1年前 评论
xin6841414

同一经线上,相差一纬度约为111km,同一纬线上,相差一经度约为111cosA (A为该纬线的纬度)km 。所以同一段距离,经度差 = 纬度差 / cosA, 所以这个 $lngR = $range / cos($latitude * pi() / 180); 是不是不对呢 是不是 $lngR = $range / cos($latitude); 这样就好了

1年前 评论

mysql geohash postgresql geohash 速度嗖嗖的

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!