2010-01-19 43 views
4

我有一個地理字段存儲在我的數據庫中,它包含一個線串路徑。在SQL Server 2008中沿着路徑移動一個點

我想沿此線條移動一個點n米,然後返回目的地。

例如,我想從目錄點開始沿線串500米。

下面是一個例子 - 什麼是YourFunctionHere?或者,還有另一種方式嗎?

DECLARE @g geography; 
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656, -122.310 47.690)', 4326); 
SELECT @g.YourFunctionHere(100).ToString(); 

回答

13

這是有點棘手,但它肯定是可能的。

我們首先計算一點到另一點的方位。給定一個起點,軸承和距離,下面的函數將返回目標點:

CREATE FUNCTION [dbo].[func_MoveTowardsPoint](@start_point geography, 
               @end_point geography, 
               @distance int) /* Meters */ 
RETURNS geography 
AS 
BEGIN 
    DECLARE @ang_dist float = @distance/6371000.0; /* Earth's radius */ 
    DECLARE @bearing decimal(18,15); 
    DECLARE @lat_1 decimal(18,15) = Radians(@start_point.Lat); 
    DECLARE @lon_1 decimal(18,15) = Radians(@start_point.Long); 
    DECLARE @lat_2 decimal(18,15) = Radians(@end_point.Lat); 
    DECLARE @lon_diff decimal(18,15) = Radians(@end_point.Long - @start_point.Long); 
    DECLARE @new_lat decimal(18,15); 
    DECLARE @new_lon decimal(18,15); 
    DECLARE @result geography; 

    /* First calculate the bearing */ 

    SET @bearing = ATN2(sin(@lon_diff) * cos(@lat_2), 
         (cos(@lat_1) * sin(@lat_2)) - 
         (sin(@lat_1) * cos(@lat_2) * 
         cos(@lon_diff))); 

    /* Then use the bearing and the start point to find the destination */ 

    SET @new_lat = asin(sin(@lat_1) * cos(@ang_dist) + 
         cos(@lat_1) * sin(@ang_dist) * cos(@bearing)); 

    SET @new_lon = @lon_1 + atn2(sin(@bearing) * sin(@ang_dist) * cos(@lat_1), 
            cos(@ang_dist) - sin(@lat_1) * sin(@lat_2)); 

    /* Convert from Radians to Decimal */ 

    SET @new_lat = Degrees(@new_lat); 
    SET @new_lon = Degrees(@new_lon); 

    /* Return the geography result */ 

    SET @result = 
     geography::STPointFromText('POINT(' + CONVERT(varchar(64), @new_lon) + ' ' + 
               CONVERT(varchar(64), @new_lat) + ')', 
            4326); 

    RETURN @result; 
END 

我知道你需要一個函數,一個線串作爲輸入,而不只是起點和終點。該點必須沿着連接線段的路徑移動,並且必須繼續在路徑的「角落」周圍移動。這聽起來有點複雜,但我認爲它可以解決如下:通過您的線串的每一點與STPointN()

  1. 迭代,從x = 1到x = STNumPoints()
  2. 查找與STDistance()當前點之間的重複,將下一個點的距離:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
  3. 如果上面的距離>您輸入的距離「N」:

    ...那麼目標點之間這一點和下一個。只需將func_MoveTowardsPoint經過點x作爲起點,點x + 1作爲終點,距離n。返回結果並中斷迭代。

    否則:

    ...目標點是進一步從迭代的下一個點的路徑。從距離'n'中減去點x和點x + 1之間的距離。以修改的距離繼續進行迭代。

您可能已經注意到,我們可以輕鬆地以遞歸方式實現上述而不是迭代。

讓我們開始吧:

CREATE FUNCTION [dbo].[func_MoveAlongPath](@path geography, 
              @distance int, 
              @index int = 1) 
RETURNS geography 
AS 
BEGIN 
    DECLARE @result  geography = null; 
    DECLARE @num_points int = @path.STNumPoints(); 
    DECLARE @dist_to_next float; 

    IF @index < @num_points 
    BEGIN 
     /* There is still at least one point further from the point @index 
      in the linestring. Find the distance to the next point. */ 

     SET @dist_to_next = @path.STPointN(@index).STDistance(@path.STPointN(@index + 1)); 

     IF @distance <= @dist_to_next 
     BEGIN 
      /* @dist_to_next is within this point and the next. Return 
       the destination point with func_MoveTowardsPoint(). */ 

      SET @result = [dbo].[func_MoveTowardsPoint](@path.STPointN(@index), 
                 @path.STPointN(@index + 1), 
                 @distance); 
     END 
     ELSE 
     BEGIN 
      /* The destination is further from the next point. Subtract 
       @dist_to_next from @distance and continue recursively. */ 

      SET @result = [dbo].[func_MoveAlongPath](@path, 
                @distance - @dist_to_next, 
                @index + 1); 
     END 
    END 
    ELSE 
    BEGIN 
     /* There is no further point. Our distance exceeds the length 
      of the linestring. Return the last point of the linestring. 
      You may prefer to return NULL instead. */ 

     SET @result = @path.STPointN(@index); 
    END 

    RETURN @result; 
END 

有了到位,是時候做一些測試。我們就用它在這一問題提供的原始線串,我們將在350米請求中的目的點,3500米和7000米:

DECLARE @g geography; 
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, 
               -122.343 47.656, 
               -122.310 47.690)', 4326); 

SELECT [dbo].[func_MoveAlongPath](@g, 350, DEFAULT).ToString(); 
SELECT [dbo].[func_MoveAlongPath](@g, 3500, DEFAULT).ToString(); 
SELECT [dbo].[func_MoveAlongPath](@g, 7000, DEFAULT).ToString(); 

我們的測試返回以下結果:

POINT (-122.3553270591861 47.6560002502638) 
POINT (-122.32676470116748 47.672728464582583) 
POINT (-122.31 47.69) 

請注意,我們要求的最後距離(7000米)超出了線條的長度,所以我們返回了最後一個點。在這種情況下,如果您願意,可以輕鬆修改函數以返回NULL。

+3

意想不到的答案。非常感謝你的幫助! – 2010-01-25 22:57:23

1

我用丹尼爾的回答上面,但我不得不修復「func_MoveAlongPath」簽名

CREATE FUNCTION [dbo].[func_MoveAlongPath](@path geography, 
             @distance **float**, 
             @index int = 1) 

的int會返回錯誤的結果,因爲它會圍繞遞歸調用中的值。 我然後轉換成一個迭代版本,因爲遞歸一個無法處理在我的樣本數據較大的距離:

CREATE FUNCTION [dbo].[func_MoveAlongPathIter](@path geography, 
               @distance float) 
RETURNS geography 
AS 
BEGIN 
    DECLARE @index   int = 1; 
    DECLARE @result   geography = null; 
    DECLARE @num_points  int = @path.STNumPoints(); 
    DECLARE @dist_to_next float; 
    DECLARE @comul_distance float = 0; 

    WHILE (@index < @num_points - 1) AND (@comul_distance < @distance) 
    BEGIN 
     SET @dist_to_next = @path.STPointN(@index).STDistance(@path.STPointN(@index + 1)); 
     SET @comul_distance += @dist_to_next; 
     SET @index += 1; 
    END 

    SET @result = [dbo].[func_MoveTowardsPoint](@path.STPointN(@index - 1), 
                 @path.STPointN(@index), 
                 @distance - (@comul_distance - @dist_to_next)); 
    RETURN @result; 
END