求最近的一家门店

已知用户坐标和商户的坐标,从一组商家中筛选离用户位置最近的一个商家。求优雅的筛选方法。
用户坐标: lat, lng
商户Model: Shop包含latitude,longitude字段

orm
本帖已被设为精华帖!
本帖由系统于 2年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

我理解的是计算直线距离 然后排序
shop表应该也有经纬度两个字段
关于后续优化 应该就是加索引了
sql 应该是这样

SELECT *,SQRT(

    POW(111.2 * (lat - 40.0844020000), 2) +

    POW(111.2 * (116.3483150000 - lng) * COS(lat / 57.3), 2)) AS distance

FROM shop  HAVING distance < 25 ORDER BY distance;

参考文章

2年前 评论
osang (楼主) 2年前
讨论数量: 28

@勇敢的心 按照大佬的提示,自己尝试实现了一下,又学习到了新姿势:

    // 以下写法只针对 predis 扩展使用,phpredis 驱动酌情修改
    $predis = (new \Illuminate\Redis\Connectors\PredisConnector)->connect(config('database.redis'), []);

    // 地址信息
    $arr = [
        [116.404053, 39.914935, '天安门'],
        [116.417977, 39.91434, '王府井地铁站'],
        [116.442285, 39.914437, '建国门地铁站'],
        [116.44162, 39.948643, '东直门地铁站'],
    ];

    // 录入经纬度和对应的名称数据
    $res = $predis->geoAdd('place', $arr);
    // dd($res);
    // true

    // 查看录入的数据
    $res = $predis->geoPos('place', ['天安门', '王府井地铁站', '建国门地铁站', '东直门地铁站']);
    // dd($res);
    // array:4 [▼
    //   0 => array:2 [▼
    //     0 => "116.40405446290969849"
    //     1 => "39.91493466874174345"
    //   ]
    //   1 => array:2 [▼
    //     0 => "116.41797512769699097"
    //     1 => "39.91433900926930534"
    //   ]
    //   2 => array:2 [▼
    //     0 => "116.44228667020797729"
    //     1 => "39.91443786339451805"
    //   ]
    //   3 => array:2 [▼
    //     0 => "116.44162148237228394"
    //     1 => "39.94864392543978937"
    //   ]
    // ]

    // 计算「天安门」与「东直门地铁站」两个地址之间的直线距离,单位:千米(km)[可选:米(m)]
    $res = $predis->geoDist('place', '天安门', '东直门地铁站', 'km');
    // dd($res);
    // "4.9319"

    // 返回与「天安门」相距 4 千米内的地点,并按照从近到远排序
    $res = $predis->geoRadiusByMember('place', '天安门', 4, 'km', [
        // 排序方式
        'SORT' => 'asc',
        // 返回的每个地点中追加经纬度
        'WITHCOORD' => true,
        // 返回的每个地点中追加距离
        'WITHDIST' => true,
        // 返回前几个地点,必须大于零,小于等于零会报错
        'COUNT' => 5,
    ]);
    // dd($res);
    // array:3 [▼
    //   0 => array:3 [▼
    //     0 => "天安门"
    //     1 => "0.0000"
    //     2 => array:2 [▼
    //       0 => "116.40405446290969849"
    //       1 => "39.91493466874174345"
    //     ]
    //   ]
    //   1 => array:3 [▼
    //     0 => "王府井地铁站"
    //     1 => "1.1894"
    //     2 => array:2 [▼
    //       0 => "116.41797512769699097"
    //       1 => "39.91433900926930534"
    //     ]
    //   ]
    //   2 => array:3 [▼
    //     0 => "建国门地铁站"
    //     1 => "3.2621"
    //     2 => array:2 [▼
    //       0 => "116.44228667020797729"
    //       1 => "39.91443786339451805"
    //     ]
    //   ]
    // ]

    // 我的位置,西单地铁站
    $myLocation = [116.380625, 39.913219];

    // 计算与我相距 4 千米内的地点,并按照从近到远排序
    $res = $predis->geoRadius('place', $myLocation[0], $myLocation[1], 4, 'km');
    // dd($res);
    // array:2 [▼
    //   0 => "天安门"
    //   1 => "王府井地铁站"
    // ]
2年前 评论

用Redis实现是不是比较好

2年前 评论
osang (楼主) 2年前
勇敢的心 (作者) 2年前
osang (楼主) 2年前
osang (楼主) 2年前
勇敢的心 (作者) 2年前
osang (楼主) 2年前
勇敢的心 (作者) 2年前

我理解的是计算直线距离 然后排序
shop表应该也有经纬度两个字段
关于后续优化 应该就是加索引了
sql 应该是这样

SELECT *,SQRT(

    POW(111.2 * (lat - 40.0844020000), 2) +

    POW(111.2 * (116.3483150000 - lng) * COS(lat / 57.3), 2)) AS distance

FROM shop  HAVING distance < 25 ORDER BY distance;

参考文章

2年前 评论
osang (楼主) 2年前

使用 Redis 的 GEO 模块,是支持地理位置计算的,适合很多场景:附近的商家等,灵活、高性能

2年前 评论
$lon = //经度
$lat = //纬度
$miles = //你的范围

$query = "SELECT *, 
( 3959 * acos( cos( radians('$lat') ) * 
cos( radians( latitude ) ) * 
cos( radians( longitude ) - 
radians('$lon') ) + 
sin( radians('$lat') ) * 
sin( radians( latitude ) ) ) ) 
AS distance FROM 表名 HAVING distance < '$miles' ORDER BY distance ASC LIMIT 0, 5"

tip: 要按公里而不是英里进行搜索,请将 3959 替换为 6371。

编辑:文档找半天- -#,源于 Google。
sites.google.com/a/benamy.info/www...
mysql 浏览的适合有高亮,怎么实际显示就没有呢?

2年前 评论
LiamHao 2年前
peryiqiao (作者) 2年前
LiamHao 2年前

@勇敢的心 按照大佬的提示,自己尝试实现了一下,又学习到了新姿势:

    // 以下写法只针对 predis 扩展使用,phpredis 驱动酌情修改
    $predis = (new \Illuminate\Redis\Connectors\PredisConnector)->connect(config('database.redis'), []);

    // 地址信息
    $arr = [
        [116.404053, 39.914935, '天安门'],
        [116.417977, 39.91434, '王府井地铁站'],
        [116.442285, 39.914437, '建国门地铁站'],
        [116.44162, 39.948643, '东直门地铁站'],
    ];

    // 录入经纬度和对应的名称数据
    $res = $predis->geoAdd('place', $arr);
    // dd($res);
    // true

    // 查看录入的数据
    $res = $predis->geoPos('place', ['天安门', '王府井地铁站', '建国门地铁站', '东直门地铁站']);
    // dd($res);
    // array:4 [▼
    //   0 => array:2 [▼
    //     0 => "116.40405446290969849"
    //     1 => "39.91493466874174345"
    //   ]
    //   1 => array:2 [▼
    //     0 => "116.41797512769699097"
    //     1 => "39.91433900926930534"
    //   ]
    //   2 => array:2 [▼
    //     0 => "116.44228667020797729"
    //     1 => "39.91443786339451805"
    //   ]
    //   3 => array:2 [▼
    //     0 => "116.44162148237228394"
    //     1 => "39.94864392543978937"
    //   ]
    // ]

    // 计算「天安门」与「东直门地铁站」两个地址之间的直线距离,单位:千米(km)[可选:米(m)]
    $res = $predis->geoDist('place', '天安门', '东直门地铁站', 'km');
    // dd($res);
    // "4.9319"

    // 返回与「天安门」相距 4 千米内的地点,并按照从近到远排序
    $res = $predis->geoRadiusByMember('place', '天安门', 4, 'km', [
        // 排序方式
        'SORT' => 'asc',
        // 返回的每个地点中追加经纬度
        'WITHCOORD' => true,
        // 返回的每个地点中追加距离
        'WITHDIST' => true,
        // 返回前几个地点,必须大于零,小于等于零会报错
        'COUNT' => 5,
    ]);
    // dd($res);
    // array:3 [▼
    //   0 => array:3 [▼
    //     0 => "天安门"
    //     1 => "0.0000"
    //     2 => array:2 [▼
    //       0 => "116.40405446290969849"
    //       1 => "39.91493466874174345"
    //     ]
    //   ]
    //   1 => array:3 [▼
    //     0 => "王府井地铁站"
    //     1 => "1.1894"
    //     2 => array:2 [▼
    //       0 => "116.41797512769699097"
    //       1 => "39.91433900926930534"
    //     ]
    //   ]
    //   2 => array:3 [▼
    //     0 => "建国门地铁站"
    //     1 => "3.2621"
    //     2 => array:2 [▼
    //       0 => "116.44228667020797729"
    //       1 => "39.91443786339451805"
    //     ]
    //   ]
    // ]

    // 我的位置,西单地铁站
    $myLocation = [116.380625, 39.913219];

    // 计算与我相距 4 千米内的地点,并按照从近到远排序
    $res = $predis->geoRadius('place', $myLocation[0], $myLocation[1], 4, 'km');
    // dd($res);
    // array:2 [▼
    //   0 => "天安门"
    //   1 => "王府井地铁站"
    // ]
2年前 评论

@LiamHao 我也有所启发

$predis->georadius("city", "116.3948", "39.9029", 2, "km"); 
// array:1 [ 
//  0 => "天安门"    
//]
2年前 评论

用redis或者es都比mysql好吧

2年前 评论

Laravel框架没办法同时使用多个DB驱动吧,如果可以的话一部分数据可以存到mongodb中,不然只能借助redis了

2年前 评论
LiamHao 2年前
LiamHao 2年前
osang (楼主) 2年前
codecodify (作者) 2年前

MYSQL里有个st_distance函数:

SELECT `shops`.*,
       (st_distance(point(longitude, latitude), point(116.4600000000, 39.9200000000)) *111195) as distance
FROM `shops`
ORDER BY distance ASC;
2年前 评论

建议使用 Redis。

// 添加门店经纬度到 Redis
Redis::geoadd(自定义key, 门店经度, 门店纬度, 门店id);

// 从 Redis 中获取附近门店
Redis::geoRadius(自定义key, 用户经度, 用户纬度, 搜索半径, 'm', ['WITHDIST', 'ASC']);
2年前 评论

我以前的做法调用百度或者高德地图接口 计算距离,比自己计算的准确

2年前 评论

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