sql >> Database teknologi >  >> RDS >> Sqlserver

Flytning af et punkt langs en sti i SQL Server 2008

Dette er en lille smule tricky, men det er bestemt muligt.

Lad os starte med at beregne pejlingen fra et punkt til et andet. Givet et startpunkt, en pejling og en afstand, vil følgende funktion returnere destinationspunktet:

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

Jeg forstår, at du kræver en funktion, der tager en linjestreng som input, ikke kun start- og slutpunkter. Punktet skal bevæge sig langs en sti af sammenkædede linjestykker og skal fortsætte med at bevæge sig rundt om "hjørnerne" af stien. Dette kan virke kompliceret i starten, men jeg tror, ​​det kan løses på følgende måde:

  1. Gener gennem hvert punkt i din linjestreng med STPointN() , fra x=1 til x=STNumPoints() .
  2. Find afstanden med STDistance() mellem det aktuelle punkt i iterationen til det næste punkt:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
  3. Hvis ovenstående afstand> din inputafstand 'n':

    ...så er destinationspunktet mellem dette punkt og det næste. Du skal blot anvende func_MoveTowardsPoint passagepunkt x som startpunkt, punkt x+1 som slutpunkt og afstand n. Returner resultatet og bryd iterationen.

    Ellers:

    ...destinationspunktet er længere i stien fra det næste punkt i iterationen. Træk afstanden mellem punkt x og punkt x+1 fra din afstand 'n'. Fortsæt gennem iterationen med den ændrede afstand.

Du har måske bemærket, at vi nemt kan implementere ovenstående rekursivt i stedet for iterativt.

Lad os gøre 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å plads, er det tid til at lave nogle tests. Lad os bruge den originale linjestreng, der blev angivet i spørgsmålet, og vi anmoder om destinationspunkterne på 350 m, 3500 m og 7000 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();

Vores test returnerer følgende resultater:

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

Bemærk, at den sidste distance vi anmodede om (7000m) overskred længden af ​​linjestrengen, så vi fik det sidste punkt tilbage. I dette tilfælde kan du nemt ændre funktionen til at returnere NULL, hvis du foretrækker det.



  1. 2 måder at liste alle triggere i en PostgreSQL-database

  2. Kortlæg oraklets type med dvale

  3. Hvordan genindlæses automatisk efter brug af identity_insert?

  4. java.sql.SQLException:Feltet 'supplier_id' har ikke en standardværdi