sql >> データベース >  >> RDS >> Sqlserver

SQLServer2008でパスに沿ってポイントを移動する

    これは少し注意が必要ですが、確かに可能です。

    あるポイントから別のポイントへの方位を計算することから始めましょう。始点、方位、距離を指定すると、次の関数が終点を返します。

    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
    

    開始点と終了点だけでなく、入力として線文字列を受け取る関数が必要であることを理解しています。ポイントは、連結された線分のパスに沿って移動する必要があり、パスの「コーナー」の周りを移動し続ける必要があります。これは最初は複雑に思えるかもしれませんが、次のように取り組むことができると思います。

    1. STPointN() 、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
    

    それが整ったら、いくつかのテストを行う時が来ました。質問で提供された元のラインストリングを使用してみましょう。350m、3500m、7000mの目的地をリクエストします。

    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)
    

    要求した最後の距離(7000m)がラインストリングの長さを超えたため、最後のポイントが返されたことに注意してください。この場合、必要に応じて、関数を簡単に変更してNULLを返すことができます。



    1. mysqlクエリの最初の行のみを取得する

    2. yiiでmysqlクエリを実行する方法

    3. 左結合を1つの結果を返すように制限しますか?

    4. Oracle Long Raw 問題