SQL:根据lat/lon寻找最近的邻居

SQL: Finding closest neighbor based on lat/lon

我有一个 table 包含 lat/lon 家咖啡馆。我想进行 SQL 查询,以提供离每家咖啡馆最近的咖啡馆。有人可以提供有关如何执行此操作的建议吗?

table基本上是这样的:

咖啡馆 ID

+-------------------------+----------------------+----------------------+
| cafe_id                 | gps_latitude         | gps_longitude        |
+-------------------------+----------------------+----------------------+
| 011-1003                | 55.86649500000000000 |  8.16856200000000000 |
| 192-143                 | 57.04419159749860000 | 10.36447024359820000 |
| 037-0233                | 55.08773849210000000 |  8.56101036070000000 |
| 121-934                 | 56.89120900000000000 |  9.16818100000000000 |
+-------------------------+----------------------+----------------------+

非常感谢任何帮助!

试试这个:

SELECT zip, primary_city, latitude, longitude,
      111.045* DEGREES(ACOS(COS(RADIANS(latpoint))
                 * COS(RADIANS(latitude))
                 * COS(RADIANS(longpoint) - RADIANS(longitude))
                 + SIN(RADIANS(latpoint))
                 * SIN(RADIANS(latitude)))) AS distance_in_km
 FROM zip
 JOIN (
     SELECT  42.81  AS latpoint,  -70.81 AS longpoint
   ) AS p ON 1=1
 ORDER BY distance_in_km;

可以在此处阅读有关此主题的更多信息: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

下面的查询有助于找出两个(邻居)最近的位置。

在 MySQL 中使用当前经纬度值设置变量

SET @lat=55.866495, @lng=8.168562;

执行查询

(SELECT tbl.cafe_id , tbl.gps_latitude, tbl.gps_longitude, 111.045 * DEGREES(ACOS(COS(RADIANS(@lat))
 * COS(RADIANS(gps_latitude))
 * COS(RADIANS(gps_longitude) - RADIANS(@lng))
 + SIN(RADIANS(@lat))
 * SIN(RADIANS(gps_latitude))))
 AS distance_in_km
FROM table_name as tbl ORDER BY distance_in_km ASC LIMIT 0,1)
UNION ALL
(SELECT tbl.cafe_id , tbl.gps_latitude, tbl.gps_longitude, 111.045 * DEGREES(ACOS(COS(RADIANS(@lat))
 * COS(RADIANS(gps_latitude))
 * COS(RADIANS(gps_longitude) - RADIANS(@lng))
 + SIN(RADIANS(@lat))
 * SIN(RADIANS(gps_latitude))))
 AS distance_in_km
FROM table_name as tbl ORDER BY distance_in_km DESC LIMIT 0,1);

您可以使用 Spherical Law of Cosines to get the distance expressed in earth-radii

有些人更喜欢Haversine formula,因为它提供更好的精度,但考虑到MySql的浮点精度足够高,两者之间的差异可以忽略不计。第一个实现起来比较简单:

select     c1.cafe_id,
           substring_index (
             group_concat( c2.cafe_id order by 
               acos(  sin(radians(c1.gps_latitude)) * sin(radians(c2.gps_latitude)) 
                    + cos(radians(c1.gps_latitude)) * cos(radians(c2.gps_latitude)) 
                      * cos(radians(c2.gps_longitude-c1.gps_longitude)) ) ),
             ',', 1) nearest
from       cafe c1
inner join cafe c2 on c1.cafe_id <> c2.cafe_id
group by   c1.cafe_id

样本数据的输出是:

|  cafe_id |  nearest |
|----------|----------|
| 011-1003 | 037-0233 |
| 037-0233 | 011-1003 |
| 121-934  | 192-143  |
| 192-143  | 121-934  |

这是一个MySql fiddle.

说明

group_concat聚合函数的order by子句中使用距离计算,得到cafe_id[=46的逗号分隔列表=] 值按照与您分组的咖啡馆的距离排序。 substring_index 函数从该列表中提取第一项。

连接条件很重要,因为没有它,您会将咖啡馆本身作为最近的邻居(那么它的距离显然为 0)。

设置距离限制

在评论中,您要求仅包含特定半径内的邻居。

在这种情况下,您可以输出 "distance",转换为公里(英里将是一个不同的因素):

select     c1.cafe_id,
           substring_index (
             group_concat( c2.cafe_id order by 
               acos(  sin(radians(c1.gps_latitude)) * sin(radians(c2.gps_latitude)) 
                    + cos(radians(c1.gps_latitude)) * cos(radians(c2.gps_latitude)) 
                      * cos(radians(c2.gps_longitude-c1.gps_longitude)) ) ),
             ',', 1) nearest,
           min(
               acos(  sin(radians(c1.gps_latitude)) * sin(radians(c2.gps_latitude)) 
                    + cos(radians(c1.gps_latitude)) * cos(radians(c2.gps_latitude)) 
                      * cos(radians(c2.gps_longitude-c1.gps_longitude)) ) )
             * 6371 km
from       cafe c1
inner join cafe c2 on c1.cafe_id <> c2.cafe_id
group by   c1.cafe_id

现在您可以根据距离决定是否要忽略邻居。如果你真的想排除最近邻居太远的咖啡馆,那么在末尾添加一个 having 子句:

having     km < 5

如果您更喜欢里程,则在 SQL 中使用 3959 作为乘数而不是 6371。