求最近的一家门店

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

orm
本帖已被设为精华帖!
本帖由系统于 2年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 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;

参考文章

3年前 评论
osang (楼主) 3年前
讨论数量: 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 => "王府井地铁站"
    // ]
3年前 评论

用Redis实现是不是比较好

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

我理解的是计算直线距离 然后排序
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;

参考文章

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

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

3年前 评论
$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 浏览的适合有高亮,怎么实际显示就没有呢?

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

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

    // 以下写法只针对 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 => "王府井地铁站"
    // ]
3年前 评论

@LiamHao 我也有所启发

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

用redis或者es都比mysql好吧

3年前 评论

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

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

MYSQL里有个st_distance函数:

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

建议使用 Redis。

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

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

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

2年前 评论

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