sql >> Database >  >> RDS >> Sqlserver

Een punt langs een pad verplaatsen in SQL Server 2008

Dit is een beetje lastig, maar het is zeker mogelijk.

Laten we beginnen met het berekenen van de peiling van het ene punt naar het andere. Gegeven een startpunt, een peiling en een afstand, zal de volgende functie het bestemmingspunt teruggeven:

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

Ik begrijp dat je een functie nodig hebt die een lijnstring als invoer heeft, niet alleen begin- en eindpunten. Het punt moet langs een pad van aaneengeschakelde lijnsegmenten bewegen en moet blijven bewegen rond de "hoeken" van het pad. Dit lijkt in eerste instantie misschien ingewikkeld, maar ik denk dat het als volgt kan worden aangepakt:

  1. Herhaal elk punt van uw lijnstring met STPointN() , van x=1 tot x=STNumPoints() .
  2. Zoek de afstand met STDistance() tussen het huidige punt in de iteratie naar het volgende punt:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
  3. Als de bovenstaande afstand> uw invoerafstand 'n':

    ...dan ligt het bestemmingspunt tussen dit en het volgende. Pas eenvoudig func_MoveTowardsPoint toe het passeren van punt x als startpunt, punt x+1 als eindpunt en afstand n. Retourneer het resultaat en verbreek de iteratie.

    Anders:

    ... het bestemmingspunt verder in het pad ligt vanaf het volgende punt in de iteratie. Trek de afstand tussen punt x en punt x+1 af van je afstand 'n'. Ga door met de iteratie met de gewijzigde afstand.

Het is je misschien opgevallen dat we het bovenstaande gemakkelijk recursief kunnen implementeren, in plaats van iteratief.

Laten we het doen:

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

Als dat op zijn plaats is, is het tijd om wat tests uit te voeren. Laten we de originele lijnstring gebruiken die in de vraag is opgegeven, en we zullen de bestemmingspunten opvragen op 350 m, op 3500 m en op 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();

Onze test geeft de volgende resultaten:

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

Merk op dat de laatste afstand die we hebben aangevraagd (7000 m) de lengte van de lijnstring overschreed, dus we kregen het laatste punt terug. In dit geval kunt u de functie eenvoudig wijzigen om NULL terug te geven, als u dat wilt.



  1. Welsprekende polymorfe relaties gebruiken om gegevens in Laravel te categoriseren

  2. runtime-fout:java.lang.ClassNotFoundException:com.mysql.jdbc.Driver

  3. Bestanden importeren van PostgreSQL naar R

  4. hoe u een update-trigger maakt voor het verhogen/verlagen van 1 nummer tot het totale aantal stemmen