sql >> Databasteknik >  >> RDS >> Sqlserver

Flytta en punkt längs en väg i SQL Server 2008

Det här är lite knepigt, men det är säkert möjligt.

Låt oss börja med att beräkna bäringen från en punkt till en annan. Givet en startpunkt, en bäring och ett avstånd, kommer följande funktion att returnera destinationspunkten:

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

Jag förstår att du kräver en funktion som tar en linjesträng som indata, inte bara start- och slutpunkter. Punkten måste röra sig längs en bana av sammanlänkade linjesegment och måste fortsätta att röra sig runt banans "hörn". Detta kan tyckas komplicerat till en början, men jag tror att det kan lösas på följande sätt:

  1. Iterera genom varje punkt i din radsträng med STPointN() , från x=1 till x=STNumPoints() .
  2. Hitta avståndet med STDistance() mellan den aktuella punkten i iterationen till nästa punkt:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
  3. Om avståndet ovan> ditt inmatade avstånd 'n':

    ...då är målpunkten mellan denna punkt och nästa. Använd bara func_MoveTowardsPoint passpunkt x som startpunkt, punkt x+1 som slutpunkt och avstånd n. Returnera resultatet och bryt iterationen.

    Annars:

    ...destinationspunkten är längre fram i banan från nästa punkt i iterationen. Subtrahera avståndet mellan punkt x och punkt x+1 från ditt avstånd 'n'. Fortsätt genom iterationen med det ändrade avståndet.

Du kanske har märkt att vi enkelt kan implementera ovanstående rekursivt istället för iterativt.

Låt oss göra det:

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

Med det på plats är det dags att göra några tester. Låt oss använda den ursprungliga radsträngen som angavs i frågan, och vi kommer att begära destinationspunkterna på 350 m, 3 500 m och 7 000 m:

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();

Vårt test ger följande resultat:

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

Observera att den sista sträckan vi begärde (7000m) överskred längden på linjesträngen, så vi fick tillbaka den sista punkten. I det här fallet kan du enkelt ändra funktionen för att returnera NULL, om du föredrar det.



  1. Hibernate:ID-generator med inkrement och Oracle Schema

  2. ON CONVERSION ERROR misslyckas med ORA-43918:Detta argument måste vara bokstavligt

  3. funktion för att kontrollera om SQLite använder journal_mode=WAL eller journal_mode=DELETE

  4. Hitta prestandafördelar med partitionering