Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Przenoszenie punktu wzdłuż ścieżki w SQL Server 2008

To trochę trudne, ale z pewnością jest możliwe.

Zacznijmy od obliczenia namiaru z jednego punktu do drugiego. Mając punkt początkowy, namiar i odległość, następująca funkcja zwróci punkt docelowy:

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

Rozumiem, że potrzebujesz funkcji, która przyjmuje jako dane wejściowe ciąg liniowy, a nie tylko punkt początkowy i końcowy. Punkt musi poruszać się po ścieżce połączonych segmentów linii i musi nadal poruszać się wokół „rogów” ścieżki. Na początku może się to wydawać skomplikowane, ale myślę, że można to rozwiązać w następujący sposób:

  1. Iteruj przez każdy punkt ciągu linii za pomocą STPointN() , od x=1 do x=STNumPoints() .
  2. Znajdź odległość za pomocą STDistance() między bieżącym punktem w iteracji a następnym punktem:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
  3. Jeśli powyższa odległość> odległość wejściowa 'n':

    ...wtedy punkt docelowy znajduje się między tym punktem a następnym. Po prostu zastosuj func_MoveTowardsPoint mijając punkt x jako punkt początkowy, punkt x+1 jako punkt końcowy i odległość n. Zwróć wynik i przerwij iterację.

    Inne:

    ...punkt docelowy znajduje się dalej na ścieżce od następnego punktu w iteracji. Odejmij odległość między punktem x a punktem x+1 od odległości 'n'. Kontynuuj iterację ze zmodyfikowaną odległością.

Być może zauważyłeś, że możemy łatwo zaimplementować powyższe rekurencyjnie, zamiast iteracyjne.

Zróbmy to:

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

Mając to na miejscu, nadszedł czas na wykonanie kilku testów. Użyjmy oryginalnego ciągu linii podanego w pytaniu i poprosimy o punkty docelowe na 350m, 3500m i 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();

Nasz test zwraca następujące wyniki:

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

Zauważ, że ostatnia odległość, o którą prosiliśmy (7000 m), przekroczyła długość linestr, więc zwrócono nam ostatni punkt. W takim przypadku możesz łatwo zmodyfikować funkcję, aby zwracała NULL, jeśli wolisz.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wyszukiwanie pełnotekstowe nie działa, jeśli dołączono słowo zatrzymania, mimo że lista słów zatrzymania jest pusta

  2. Jak określić liczbę dni w miesiącu w SQL Server?

  3. Case Statement w SQL za pomocą Like

  4. SQL Server 2008 - utwórz skrypt bazy danych (schemat + dane) za pomocą wiersza poleceń

  5. T-SQL Jak dynamicznie tworzyć tabele w procedurach składowanych?