在mysql中使用纬度和经度查找两点之间的距离
Posted
技术标签:
【中文标题】在mysql中使用纬度和经度查找两点之间的距离【英文标题】:Find distance between two points using latitude and longitude in mysql 【发布时间】:2014-08-13 18:59:40 【问题描述】:您好,我有下表
--------------------------------------------
| id | city | Latitude | Longitude |
--------------------------------------------
| 1 | 3 | 34.44444 | 84.3434 |
--------------------------------------------
| 2 | 4 | 42.4666667 | 1.4666667 |
--------------------------------------------
| 3 | 5 | 32.534167 | 66.078056 |
--------------------------------------------
| 4 | 6 | 36.948889 | 66.328611 |
--------------------------------------------
| 5 | 7 | 35.088056 | 69.046389 |
--------------------------------------------
| 6 | 8 | 36.083056 | 69.0525 |
--------------------------------------------
| 7 | 9 | 31.015833 | 61.860278 |
--------------------------------------------
现在我想得到两点之间的距离。假设一个用户拥有一个城市 3,一个用户拥有一个城市 7。我的场景是一个用户拥有一个城市和纬度和经度,正在搜索其他用户与他的城市的距离。例如,拥有城市 3 的用户正在搜索。他想得到任何其他城市的用户的距离说它是 7。我已经搜索并找到了以下查询
SELECT `locations`.`city`, ( 3959 * acos ( cos ( radians(31.589167) ) * cos( radians( Latitude ) ) * cos( radians( Longitude ) - radians(64.363333) ) + sin ( radians(31.589167) ) * sin( radians( Latitude ) ) ) ) AS `distance` FROM `locations` HAVING (distance < 50)
据我所知,此查询查找从一个点到所有其他点的距离。现在我想获得从一点到另一点的距离。
任何指南都将不胜感激。
【问题讨论】:
所以对表格进行自连接以获得两组坐标,然后您只需对这两点进行计算。 您是否考虑过将其从 SQL 查询中取出,并为此使用 Google Maps API?我知道它没有回答你的问题。但是您可能会发现这项工作已经为您完成了。 @MarcB 没明白你的意思,亲爱的 @durbnpoisn 不,我不想要那个解决方案。这是我的要求。 @MarcB 此查询将循环运行,因此我将获得两个点的值。但是如何输入此查询并不容易理解。 【参考方案1】:我认为您的问题是您希望计算距离的两个城市的 city
值。
此查询将为您完成这项工作,产生以公里为单位的距离。它使用球余弦定律公式。
请注意,您将表连接到自身,以便检索两个坐标对进行计算。
SELECT a.city AS from_city, b.city AS to_city,
111.111 *
DEGREES(ACOS(LEAST(1.0, COS(RADIANS(a.Latitude))
* COS(RADIANS(b.Latitude))
* COS(RADIANS(a.Longitude - b.Longitude))
+ SIN(RADIANS(a.Latitude))
* SIN(RADIANS(b.Latitude))))) AS distance_in_km
FROM city AS a
JOIN city AS b ON a.id <> b.id
WHERE a.city = 3 AND b.city = 7
请注意,常数111.1111
是每纬度的公里数,根据旧拿破仑将米定义为赤道到极点距离的千分之一。这个定义对于定位器工作来说已经足够接近了。
如果您想要法定英里而不是公里,请改用69.0
。
http://sqlfiddle.com/#!9/21e06/412/0
如果您正在寻找附近的点,您可能会想使用类似这样的子句:
HAVING distance_in_km < 10.0 /* slow ! */
ORDER BY distance_in_km DESC
那是(正如我们在美国马萨诸塞州波士顿附近所说的那样)非常缓慢。
在这种情况下,您需要使用边界框计算。请参阅这篇关于如何做到这一点的文章。 http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
公式包含LEAST()
函数。为什么?因为如果 ACOS()
函数的参数甚至略大于 1,它就会引发错误。当所讨论的两个点非常接近时,使用 COS()
和 SIN()
计算的表达式有时会产生一个略大于1 由于floating-point epsilon (inaccuracy)。 LEAST(1.0, dirty-great-expression)
电话解决了这个问题。
有一个更好的方法,formula by Thaddeus Vincenty。它使用 ATAN2()
而不是 ACOS()
,因此它不太容易受到 epsilon 问题的影响。
编辑 2022(作者 Alexio Vay): 到今天为止,现代解决方案应该是以下短代码:
select ST_Distance_Sphere(
point(-87.6770458, 41.9631174),
point(-73.9898293, 40.7628267))
请查看 Naresh Kumar 的答案。
【讨论】:
您在此处和在 sqlfiddle 上写的内容不同。虽然两者都有效,但 sqlfiddle 一个给出了准确的结果。 使用代码查询2个坐标最近的位置。效果很好! 在 mysql 中使用这个公式时,我得到了非常近的距离的空值。我的灵魂是添加一个“LEAST”功能来防止超出范围的数字。度数(ACOS(LEAST(COS(RADIANS(lat)) * COS(RADIANS(google_lat)) * COS(RADIANS(lng - google_lng)) + SIN(RADIANS(lat)) * SIN(RADIANS(google_lat)),1.0) )) AS distance_in_km, 感谢您的回答。你能解释一下使用最少功能和插入 1.0 的原因吗? makewhite,好问题!请参阅@Robert 的评论和我最近的编辑。【参考方案2】:您可以使用ST_Distance_Sphere MySql 内置函数。 它可以更有效地计算以米为单位的距离。
自 MySql 5.7 及以上版本开始支持。
select ST_Distance_Sphere(point(lon,lat), point(lon,lat))
select ST_Distance_Sphere(
point(-87.6770458, 41.9631174),
point(-73.9898293, 40.7628267))
转自Calculating distance using MySQL
【讨论】:
这需要更多的方法和当今公认的答案。此解决方案是现代解决方案。【参考方案3】:这是MySQL查询和函数,用于获取两个经纬度之间的距离,距离将以KM为单位。
Mysql 查询:-
SELECT (6371 * acos(
cos( radians(lat2) )
* cos( radians( lat1 ) )
* cos( radians( lng1 ) - radians(lng2) )
+ sin( radians(lat2) )
* sin( radians( lat1 ) )
) ) as distance from your_table
Mysql 函数:-
DELIMITER $$
CREATE FUNCTION `getDistance`(`lat1` VARCHAR(200), `lng1` VARCHAR(200), `lat2` VARCHAR(200), `lng2` VARCHAR(200)) RETURNS varchar(10) CHARSET utf8
begin
declare distance varchar(10);
set distance = (select (6371 * acos(
cos( radians(lat2) )
* cos( radians( lat1 ) )
* cos( radians( lng1 ) - radians(lng2) )
+ sin( radians(lat2) )
* sin( radians( lat1 ) )
) ) as distance);
if(distance is null)
then
return '';
else
return distance;
end if;
end$$
DELIMITER ;
如何在你的 php 代码中使用
SELECT getDistance($lat1,$lng1,$lat2,$lng2) as distance
FROM your_table.
【讨论】:
差不多十年后,这个函数给出了与谷歌地图距离测量相同的结果。谢谢!【参考方案4】:这是一个 MySQL 函数,它将采用两个纬度/经度对,并为您提供两点之间的度数距离。它使用Haversine 公式来计算距离。由于地球不是一个完美的球体,在两极和赤道附近存在一些误差。
要转换为英里,请乘以 3961。 要转换为公里,请乘以 6373。 要转换为米,请乘以 6373000。 要转换为英尺,请乘以 (3961 * 5280) 20914080。DELIMITER $$
CREATE FUNCTION \`haversine\`(
lat1 FLOAT, lon1 FLOAT,
lat2 FLOAT, lon2 FLOAT
) RETURNS float
NO SQL
DETERMINISTIC
COMMENT 'Returns the distance in degrees on the Earth between two known points of latitude and longitude. To get miles, multiply by 3961, and km by 6373'
BEGIN
RETURN DEGREES(ACOS(
COS(RADIANS(lat1)) *
COS(RADIANS(lat2)) *
COS(RADIANS(lon2) - RADIANS(lon1)) +
SIN(RADIANS(lat1)) * SIN(RADIANS(lat2))
));
END;
DELIMITER;
【讨论】:
请注意,3961
的值是您向北或向南移动一弧度时在地球表面上移动的距离。
我认为是 6371 而不是 6373。
如果要转换为英里/公里/等,请不要使用 DEGREES 调用,转换因子以 RADIANS 为单位,因此您不想在乘以转换因子之前转换为度数。
感谢您提供有关转换的信息【参考方案5】:
不确定你的距离计算是如何进行的,但你需要在你的桌子上做一个self join
并相应地执行计算。大概是这样的
select t1.id as userfrom,
t2.id as userto,
( 3959 * acos ( cos ( radians(31.589167) ) * cos( radians( t1.Latitude ) ) *
cos( radians( t1.Longitude ) - radians(64.363333) ) + sin ( radians(31.589167) ) *
sin( radians( t2.Latitude ) ) ) ) AS `distance`
from table1 t1
inner join table1 t2 on t2.city > t1.city
【讨论】:
【参考方案6】:这是我从https://www.geodatasource.com/developers/javascript转换而来的公式
这是一个很干净的函数,可以计算公里数
DELIMITER $$
CREATE DEFINER=`root`@`localhost` FUNCTION `FN_GET_DISTANCE`(
lat1 DOUBLE, lng1 DOUBLE, lat2 DOUBLE, lng2 DOUBLE
) RETURNS double
BEGIN
DECLARE radlat1 DOUBLE;
DECLARE radlat2 DOUBLE;
DECLARE theta DOUBLE;
DECLARE radtheta DOUBLE;
DECLARE dist DOUBLE;
SET radlat1 = PI() * lat1 / 180;
SET radlat2 = PI() * lat2 / 180;
SET theta = lng1 - lng2;
SET radtheta = PI() * theta / 180;
SET dist = sin(radlat1) * sin(radlat2) + cos(radlat1) * cos(radlat2) * cos(radtheta);
SET dist = acos(dist);
SET dist = dist * 180 / PI();
SET dist = dist * 60 * 1.1515;
SET dist = dist * 1.609344;
RETURN dist;
END$$
DELIMITER ;
您还可以在网站上找到不同语言的相同功能;
【讨论】:
【参考方案7】:重要!任何使用或复制这些计算的人务必在将计算传递给acos()
函数时使用least(1.0, (...))
。 acos()
函数的值不会超过 1,我发现在比较相同的 lat/lng 值时,有时会得出类似 1.000002
的结果。这将产生NULL
而不是0
的距离,并且可能不会返回您正在寻找的结果,具体取决于查询的结构!
这是正确的:
select round(
( 3959 * acos( least(1.0,
cos( radians(28.4597) )
* cos( radians(lat) )
* cos( radians(lng) - radians(77.0282) )
+ sin( radians(28.4597) )
* sin( radians(lat)
) ) )
), 1) as distance
from locations having distance <= 60 order by distance
这是错误的:
select round(
( 3959 * acos(
cos( radians(28.4597) )
* cos( radians(lat) )
* cos( radians(lng) - radians(77.0282) )
+ sin( radians(28.4597) )
* sin( radians(lat)
) )
), 1) as distance
from locations having distance <= 60 order by distance
评分最高的答案也谈到了这一点,但我想确保这一点非常清楚,因为我刚刚在我的查询中发现了一个长期存在的错误。
【讨论】:
【参考方案8】:也许有人会派上用场,我设法通过 FN_GET_DISTANCE 函数实现了我的任务:
SELECT SUM (t.distance) as Distance FROM
(SELECT (CASE WHEN (FN_GET_DISTANCE (Latitude, Longitude, @OLDLatitude, @OLDLongitude)) BETWEEN 0.01 AND 2 THEN
FN_GET_DISTANCE (Latitude, Longitude, @OLDLatitude, @OLDLongitude) ELSE 0 END) AS distance,
IF (@OLDLatitude IS NOT NULL, @OLDLatitude: = Latitude, 0),
IF (@OLDLongitude IS NOT NULL, @OLDLongitude: = Longitude, 0)
FROM `data`, (SELECT @OLDLatitude: = 0) var0, (SELECT @OLDLongitude: = 0) var1
WHERE ID_Dev = 1
AND DateTime BETWEEN '2021-05-23 08:00:00' AND '2021-05-23 20:00:00'
ORDER BY ID DESC) t;
【讨论】:
以上是关于在mysql中使用纬度和经度查找两点之间的距离的主要内容,如果未能解决你的问题,请参考以下文章