关于应用中根据距离排序的问题

距离计算使用了 redis 的 GEO

目前使用的方案是 在模型添加时 在 redis 中存储包含模型 id 的经纬度信息。
查询的时候携带 经纬度信息 从 redis 取出距离然后逐个添加到模型上,
要根据距离排序的话使用了 sql 语句 orderBy('FIELD(id, 1,2,3,4....,1000)') 如果数据过多的话会导致查询巨慢。
有没有什么好的距离排序的解决方案可以用。

总持不遗,若注瓶水
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 6

可以考虑添加个 mysql 函数

# mysql5.7的  mysql8的应该不兼容 要修改下
DELIMITER $$
CREATE DEFINER=`ym`@`localhost` FUNCTION `getDistance`(
     lng1 float(10,7) 
    ,lat1 float(10,7)
    ,lng2 float(10,7) 
    ,lat2 float(10,7)
) RETURNS double
begin
    declare d double;
    declare radius int;
    set radius = 6378140; #假设地球为正球形,直径为6378140米
    set d = (2*ATAN2(SQRT(SIN((lat1-lat2)*PI()/180/2)   
        *SIN((lat1-lat2)*PI()/180/2)+   
        COS(lat2*PI()/180)*COS(lat1*PI()/180)   
        *SIN((lng1-lng2)*PI()/180/2)   
        *SIN((lng1-lng2)*PI()/180/2)),   
        SQRT(1-SIN((lat1-lat2)*PI()/180/2)   
        *SIN((lat1-lat2)*PI()/180/2)   
        +COS(lat2*PI()/180)*COS(lat1*PI()/180)   
        *SIN((lng1-lng2)*PI()/180/2)   
        *SIN((lng1-lng2)*PI()/180/2))))*radius;
    return d;
end$$
DELIMITER ;

调用:

Model::OrderBy('distance','asc')
               ->get(DB::raw("latitude,longitude,cast( (getDistance('$request->latitude','$request->longitude',latitude,longitude) ) AS SIGNED) as  distance"));
3年前 评论
$distance = '6378.137 * 2 * ASIN(SQRT(POW(SIN(((s.`lat` - :lat) * PI() / 180)/2),2) + COS(s.`lat` * PI()/180) * COS(:lat * PI()/180) * POW(SIN(((s.`lng` * PI()/180) - (:lng * PI()/180))/2),2)))';
if (!$lng || !$lat)
{
    $distance = -1;
}

ORDER BY {$distance} ASC

我只是个搬运工,以前的项目别人写的,我也不知道这是啥算法,纯MySQL即可。

3年前 评论
Adachi (作者) 3年前
fatrbaby

可以切换成postgresql,天然支持地理坐标,哈哈。

3年前 评论
yhb598712 3年前

我是用 mysql 自带函数, 有版本限制
st_distance_sphere( point(longitude1, latitude1), point(longitude2,latitude2) )

3年前 评论

这个试下,不过有点问题,经度存在跨越东西半球时候的计算问题,所以限制了范围。
坐标在国内,不在太平洋上应该没问题。

$longitude = (float) Arr::get($location, 0, 0);     // 经度
$latitude = (float) Arr::get($location, 1, 0);      // 纬度
$distance = abs(Arr::get($location, 2, 5));         // 距离

if ($longitude && $latitude && $distance) {
    // 距离不能超过 100km
    $distance = $distance > 100 ? 5 : $distance;

    // 地球平均半径 6371km
    $raw = Str::replaceArray('?', [$latitude, $longitude, $latitude], '6371 * acos(
            cos( radians( ? ) )
            * cos( radians( `latitude` ) )
            * cos( radians( `longitude` ) - radians( ? ) )
            + sin( radians( ? ) )
            * sin( radians( `latitude` ) )
        )');

    // 地球平均周长 6371km
    $PI = 3.14159265;
    $degree = 40075016 / 360;

    // 经度范围
    $mpdLng = $degree * cos($latitude * ($PI / 180));
    $radiusLng = $distance * 1000 / $mpdLng;
    $minLng = $longitude - $radiusLng;
    $maxLng = $longitude + $radiusLng;

    // 纬度范围
    $radiusLat = $distance * 1000 / $degree;
    $minLat = $latitude - $radiusLat;
    $maxLat = $latitude + $radiusLat;

    $query->selectSub($raw, 'distance')
        ->where('longitude', '<>', 0)
        ->where('latitude', '<>', 0)
        ->whereBetween('longitude', [$minLng, $maxLng])
        ->whereBetween('latitude', [$minLat, $maxLat])
        ->having('distance', '<', $distance)
        ->orderBy('distance');
}
3年前 评论

可以用 ElasticSearch

3年前 评论

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