【问题标题】:Calculating distance between two points (Latitude, Longitude)计算两点之间的距离(纬度,经度)
【发布时间】:2012-10-13 03:54:10
【问题描述】:

我正在尝试计算地图上两个位置之间的距离。 我在我的数据中存储了:经度、纬度、X POS、Y POS。

我之前一直在使用下面的sn-p。

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。你也可以在地球半径上有更高的精度,以及使用一些 Datum 使用例如通过 GPS 系统 (WGS-84) 通过椭圆体(在赤道和两极具有不同的半径)逼近地球
  • @Waller,你为什么不使用地理/几何(空间)类型来实现这一点?
  • 我用 Mathematica 检查了你的计算;它认为法定英里(5280 英尺)的距离是 42.997,这表明您的计算并非略微不准确,而是非常不准确

标签: sql sql-server tsql math sql-server-2008-r2


【解决方案1】:

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

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

【讨论】:

  • 供参考:STDistance() 以定义地理数据的空间参考系统的线性测量单位返回距离。您使用的是 SRID 4326,这意味着 STDistance() 以米为单位返回距离。
【解决方案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)

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

我希望这可能对某人有所帮助。

【讨论】:

    【解决方案3】:
    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 参数。

    【讨论】:

    • 完美运行
    【解决方案4】:

    以下函数给出两个地理坐标之间的距离(以英里为单位)

    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

    【讨论】:

    • 我需要根据各种事件的邮政编码对 35K 邮政编码进行距离计算,这些邮政编码按与邮政编码的距离排序。坐标列表太大,无法使用地理数据类型进行计算。当我切换到使用上面基于单线三角函数的解决方案时,它的运行速度要快得多。因此,仅使用地理类型来计算距离似乎很昂贵。买家小心。
    • 对于在查询的 WHERE 子句中计算距离非常有用。我确实必须在“set @d=”表达式周围包裹一个 ABS(),因为我发现了一些函数返回负距离的情况。
    • 如果我们比较 2 个相等的点,“两个地理坐标之间的距离以公里为单位”的功能会失败,它会给您错误“发生无效的浮点操作”
    • 这很好,但不适用于短距离,因为“decimal(8,4)”不能提供足够的精度。
    • @influent 是正确的,这对于短距离(在我的情况下为 5 英里)没有用
    【解决方案5】:

    由于您使用的是 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
    

    【讨论】:

    • @nezam no - Prime Meridian 以西的地方经度为负数,以东的地方经度为正
    • 你拯救了我的一天!..非常感谢!
    • 使用内置函数似乎很慢。例如,在 100,000 个项目的循环中,我的用户定义函数需要 23 秒而不是 1.4 秒(请参阅 Durai 的回答)。
    • 只是想插话并确认@AakashM 对空间索引的建议 + ... 对于 ETL 应用程序,实施空间索引后差异要好几个数量级
    • 仅供参考:POINT(LONGITUDE LATITUDE) 而 geography::Point(LATITUDE, LONGITUDE, 4326)
    【解决方案6】:

    当您使用 SQL 2008 或更高版本时,我建议您查看 GEOGRAPHY 数据类型。 SQL 内置了对地理空间查询的支持。

    例如您的表中有一个 GEOGRAPHY 类型的列,该列将填充坐标的地理空间表示(查看上面链接的 MSDN 参考以获取示例)。然后,此数据类型公开允许您执行大量地理空间查询(例如查找 2 点之间的距离)的方法

    【讨论】:

    • 补充一下,我尝试了地理字段类型,但发现使用 Durai 的函数(直接使用经度和纬度值)会快得多。在此处查看我的示例:*.com/a/37326089/391605