计算两点之间的距离(纬度,经度)

108

我正在尝试计算地图上两个位置之间的距离。

我的数据中存储了经度、纬度、X坐标、Y坐标。

我以前使用的是下面的代码片段。

DECLARE @orig_lat DECIMAL
DECLARE @orig_lng DECIMAL
SET @orig_lat=53.381538 set @orig_lng=-1.463526
SELECT *,
    3956 * 2 * ASIN(
          SQRT( POWER(SIN((@orig_lat - abs(dest.Latitude)) * pi()/180 / 2), 2) 
              + COS(@orig_lng * pi()/180 ) * COS(abs(dest.Latitude) * pi()/180)  
              * POWER(SIN((@orig_lng - dest.Longitude) * pi()/180 / 2), 2) )) 
          AS distance
--INTO #includeDistances
FROM #orig dest

然而,我不信任这里的数据,因为它似乎提供了略微不准确的结果。

以下是一些示例数据,如果需要的话:

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

能否有人帮我调整一下代码?如果您有新的方法来实现这个问题,那就更好了。对于您已经修复的内容,我不介意。

请说明您的结果使用的是哪种计量单位。


你不应该将正弦函数的参数除以额外的/2。此外,您可以在地球半径上获得更高的精度,并使用一些由GPS系统(如WGS-84)使用的“基准面”,该基准面通过椭球体(在赤道和极点处具有不同的半径)来近似地球。 - Aki Suihkonen
@Waller,为什么不使用地理/几何(空间)类型来实现这个? - Habib
3
我用Mathematica检查了你的计算;它认为英里(5280英尺)的距离是42.997,这表明你的计算不是稍微不准确,而是极其不准确 - High Performance Mark
6个回答

144

由于你正在使用 SQL Server 2008,你可以使用可用的 geography 数据类型,该数据类型专门设计用于处理此类数据:

DECLARE @source geography = 'POINT(0 51.5)'
DECLARE @target geography = 'POINT(-3 56)'

SELECT @source.STDistance(@target)

提供

----------------------
538404.100197555

(1 row(s) affected)

这告诉我们从(近)伦敦到(近)爱丁堡大约有538公里。

当然,首先需要学习一定的知识,但一旦掌握,它比编写自己的Haversine计算要简单得多; 而且你会得到很多功能。


如果您想保留现有的数据结构,仍然可以使用STDistance,通过使用Point方法构建适当的Geography实例:

DECLARE @orig_lat DECIMAL(12, 9)
DECLARE @orig_lng DECIMAL(12, 9)
SET @orig_lat=53.381538 set @orig_lng=-1.463526

DECLARE @orig geography = geography::Point(@orig_lat, @orig_lng, 4326);

SELECT *,
    @orig.STDistance(geography::Point(dest.Latitude, dest.Longitude, 4326)) 
       AS distance
--INTO #includeDistances
FROM #orig dest

6
不需要,谢谢。 经度为本初子午线(英文名称:Prime Meridian)以西的地方会是负数,而在其以东的地方则为正数。 - AakashM
1
使用内置函数似乎非常慢。例如,在一个包含100,000个项目的循环中,它需要23秒,而不是我用户定义的函数(请参见Durai的答案)所需的1.4秒。 - NickG
@NickG 空间索引可能会有所帮助。还要注意,哈弗辛公式是用于球体上的大圆距离计算,而地球并不是一个球体。STDistance更好(并链接到其他内容)。 - AakashM
1
只是想插一句话并确认@AakashM的空间索引建议+...对于ETL应用程序,在实施空间索引后,差异提高了几个数量级。 - Bill Anton
3
FYI: POINT(经度,纬度)对应于 geography::Point(纬度,经度,4326) - Lzh
显示剩余4条评论

50

下面的函数计算两个地理坐标之间的距离,单位为英里。

create function [dbo].[fnCalcDistanceMiles] (@Lat1 decimal(8,4), @Long1 decimal(8,4), @Lat2 decimal(8,4), @Long2 decimal(8,4))
returns decimal (8,4) as
begin
declare @d decimal(28,10)
-- Convert to radians
set @Lat1 = @Lat1 / 57.2958
set @Long1 = @Long1 / 57.2958
set @Lat2 = @Lat2 / 57.2958
set @Long2 = @Long2 / 57.2958
-- Calc distance
set @d = (Sin(@Lat1) * Sin(@Lat2)) + (Cos(@Lat1) * Cos(@Lat2) * Cos(@Long2 - @Long1))
-- Convert to miles
if @d <> 0
begin
set @d = 3958.75 * Atan(Sqrt(1 - power(@d, 2)) / @d);
end
return @d
end 

下面的函数可以计算两个地理坐标之间的距离,单位为千米。

CREATE FUNCTION dbo.fnCalcDistanceKM(@lat1 FLOAT, @lat2 FLOAT, @lon1 FLOAT, @lon2 FLOAT)
RETURNS FLOAT 
AS
BEGIN

    RETURN ACOS(SIN(PI()*@lat1/180.0)*SIN(PI()*@lat2/180.0)+COS(PI()*@lat1/180.0)*COS(PI()*@lat2/180.0)*COS(PI()*@lon2/180.0-PI()*@lon1/180.0))*6371
END

以下函数使用在SQL Server 2008中引入的 Geography 数据类型,计算两个地理坐标之间的距离(以公里为单位)。

DECLARE @g geography;
DECLARE @h geography;
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);
SET @h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);
SELECT @g.STDistance(@h);

用法:

select [dbo].[fnCalcDistanceKM](13.077085,80.262675,13.065701,80.258916)

参考资料: Ref1,Ref2


2
我需要对35K个邮政编码与各种事件的邮政编码进行距离计算,并按距离排序。使用地理数据类型进行计算的坐标列表太大了。当我改用基于单行三角函数的解决方案时,速度快多了。因此,仅仅为了计算距离而使用地理类型似乎是很昂贵的。买家要谨慎。 - Tombala
2
如果我们比较两个相同的点,计算两个地理坐标之间距离的函数会失败,并显示错误信息“发生了无效的浮点运算”。 - RRM
2
这很棒,但对于短距离不起作用,因为“decimal(8,4)”不提供足够的精度。 - influent
1
你为什么不直接使用fnCalcDistanceKM(不使用Geography),然后将距离除以1.609344从公里转换为英里?似乎英里计算器过于复杂,需要修改和重新分配值并进行检查。 - Trevor F
1
@influent 是正确的,对于短距离(在我的情况下是5英里)没有用处。 - Roger
显示剩余2条评论

31

看起来微软侵入了其他回答者的大脑,让他们写出了尽可能复杂的解决方案。 这里是最简单的方式,不需要任何额外的函数/声明语句:

SELECT geography::Point(LATITUDE_1, LONGITUDE_1, 4326).STDistance(geography::Point(LATITUDE_2, LONGITUDE_2, 4326))

只需将您的数据替换为LATITUDE_1LONGITUDE_1LATITUDE_2LONGITUDE_2即可,例如:

SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c

4
供参考:STDistance() 函数返回与地理数据定义的空间参考系统的线性单位相对应的距离。您正在使用 SRID 4326,这意味着 STDistance() 函数返回的距离以米为单位。 - Bryan Stump
这给了我一个错误:“地理类型不是已定义的系统类型。” 我使用的是SQL Server 2014。 有什么解决办法吗? - Jem
当您使用可能包含NULL的表中的输入数据时,会出现错误:'geography::Point'失败,因为不允许参数1为空。 - Allie
@Allie 显然,你需要添加一个额外的检查来确保两个参数都不为null。 - Stalinko

9
Create Function [dbo].[DistanceKM] 
( 
      @Lat1 Float(18),  
      @Lat2 Float(18), 
      @Long1 Float(18), 
      @Long2 Float(18)
)
Returns Float(18)
AS
Begin
      Declare @R Float(8); 
      Declare @dLat Float(18); 
      Declare @dLon Float(18); 
      Declare @a Float(18); 
      Declare @c Float(18); 
      Declare @d Float(18);
      Set @R =  6367.45
            --Miles 3956.55  
            --Kilometers 6367.45 
            --Feet 20890584 
            --Meters 6367450 


      Set @dLat = Radians(@lat2 - @lat1);
      Set @dLon = Radians(@long2 - @long1);
      Set @a = Sin(@dLat / 2)  
                 * Sin(@dLat / 2)  
                 + Cos(Radians(@lat1)) 
                 * Cos(Radians(@lat2))  
                 * Sin(@dLon / 2)  
                 * Sin(@dLon / 2); 
      Set @c = 2 * Asin(Min(Sqrt(@a))); 

      Set @d = @R * @c; 
      Return @d; 

End
GO

用法:

选择 dbo.DistanceKM(37.848832506474, 37.848732506474, 27.83935546875, 27.83905546875)

输出:

0.02849639

您可以使用注释的浮点数更改 @R 参数。


完美运行 - Tejasvi Hegde

4
由于您使用的是SQL 2008或更高版本,我建议您查看 GEOGRAPHY数据类型。 SQL内置支持地理空间查询。
例如,您可以在表中拥有一个GEOGRAPHY类型的列,该列将填充具有坐标的地理空间表示形式(请查看上面链接的MSDN参考示例)。然后,此数据类型公开了许多地理空间查询方法(例如查找两点之间的距离)。

只是补充一下,我尝试了地理字段类型,但发现使用Durai的函数(直接使用经纬度值)速度要快得多。请参见我的示例:https://dev59.com/i3TYa4cB1Zd3GeqPskEM#37326089 - Mike Gledhill

2
除了之前的回答,这里有一种在SELECT内计算距离的方法:
CREATE FUNCTION Get_Distance
(   
    @La1 float , @Lo1 float , @La2 float, @Lo2 float
)
RETURNS TABLE 
AS
RETURN 
    -- Distance in Meters
    SELECT GEOGRAPHY::Point(@La1, @Lo1, 4326).STDistance(GEOGRAPHY::Point(@La2, @Lo2, 4326))
    AS Distance
GO

使用方法:

select Distance
from Place P1,
     Place P2,
outer apply dbo.Get_Distance(P1.latitude, P1.longitude, P2.latitude, P2.longitude)

标量函数也可以使用,但在计算大量数据时非常低效。

我希望这能帮到某些人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接