sql >> Databáze >  >> RDS >> Sqlserver

Přesunutí bodu po cestě v SQL Server 2008

Je to trochu složité, ale možné to určitě je.

Začněme výpočtem azimutu z jednoho bodu do druhého. Zadaný počáteční bod, směr a vzdálenost, následující funkce vrátí cílový bod:

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

Chápu, že potřebujete funkci, která bere jako vstup čárový řetězec, nikoli pouze počáteční a koncové body. Bod se musí pohybovat po dráze zřetězených úsečkových segmentů a musí pokračovat v pohybu kolem "rohů" cesty. Na první pohled se to může zdát složité, ale myslím, že to lze vyřešit následovně:

  1. Procházejte každý bod řetězce pomocí STPointN() , od x=1 do x=STNumPoints() .
  2. Zjistěte vzdálenost pomocí STDistance() mezi aktuálním bodem v iteraci k dalšímu bodu:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
  3. Pokud výše uvedená vzdálenost> vaše vstupní vzdálenost 'n':

    ...potom je cílový bod mezi tímto a následujícím bodem. Jednoduše použijte func_MoveTowardsPoint průchozí bod x jako počáteční bod, bod x+1 jako koncový bod a vzdálenost n. Vraťte výsledek a přerušte iteraci.

    Jinak:

    ...cílový bod je dále v cestě od dalšího bodu v iteraci. Odečtěte vzdálenost mezi bodem x a bodem x+1 od vaší vzdálenosti 'n'. Pokračujte v iteraci s upravenou vzdáleností.

Možná jste si všimli, že výše uvedené můžeme snadno implementovat rekurzivně, místo iterativně.

Pojďme na 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

Když je to na místě, je čas udělat nějaké testy. Použijme původní řetězec, který byl poskytnut v otázce, a vyžádáme si cílové body ve 350 m, 3 500 m a 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();

Náš test vrací následující výsledky:

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

Všimněte si, že poslední vzdálenost, kterou jsme požadovali (7000 m), přesáhla délku šňůry, takže nám byl vrácen poslední bod. V tomto případě můžete snadno upravit funkci tak, aby vracela NULL, pokud chcete.



  1. Volání funkce Oracle PLSQL do druhé funkce

  2. Proč má Oracle varchar2 povinnou velikost jako definiční parametr?

  3. Dokážete vyřešit tento jednoduchý SQL dotaz?

  4. SQL dotaz:Načtení uspořádaných řádků z tabulky - II