Определите, находится ли ТОЧКА между двумя другими ТОЧКАМИ НА СТРОКЕ (география SQL Server 2008)

У меня SQL Server 2008 GEOGRAPHY тип данных в моей базе данных, содержащей LINESTRING, LINESTRING описывает потенциально извилистую дорогу.

У меня есть другая таблица, которая содержит начальную и конечную точки, оба GEOGRAPHYPOINT на дороге. Мне нужно знать, если третья точка попадает между этими двумя точками на дороге (LINESTRING).

В настоящее время я тестирую это:

  • третья точка на линии
  • расстояние между новой точкой до начальной точки и расстояние между новой точкой и конечной точкой меньше расстояния между начальной точкой и конечной точкой

Это работает, но кажется, что действительно не элегантно не работает вообще, если дорога разворачивается сама собой! Есть ли способ, который работает?

2 ответа

Решение

Как вы заметили, ваш метод завершится ошибкой в ​​следующем случае, где S - начальная точка, E - конечная точка, а X - точка, которую вы тестируете:

Определите, находится ли ТОЧКА между двумя другими ТОЧКАМИ НА СТРОКЕ http://img10.imageshack.us/img10/4937/gmap.png

Используя этот метод, точка X ложно окажется между точкой S и точкой E, потому что она проходит и тест 1, и тест 2 вашего алгоритма: т.е. Точка X находится на линейной линии, а расстояния от X до S и от X до E меньше, чем расстояние от S до E.


Одно из возможных решений

Вы можете "взорвать" свой путь строки в отдельные линейные сегменеты всего двумя точками в каждой, чтобы:

LINESTRING(-122.360 47.656, -122.343 47.656, -122.310 47.690, -122.310 47.670)

будет разбит на:

LINESTRING(-122.360 47.656, -122.343 47.656)
LINESTRING(-122.343 47.656, -122.310 47.690)
LINESTRING(-122.310 47.690, -122.310 47.670)

Тогда вы сможете перебирать каждый из указанных выше отрезков и проверять, лежит ли точка на одном из этих отрезков, используя STIntersects, Когда точка пройдет этот тест, вы сможете определить, находилась ли она в пределах начальной и конечной точек.

Если возможно, я бы предложил сохранить начальную / конечную точки в качестве индекса для точки на пути линейных линий вместо исходной географической точки. Во-первых, это облегчит решение этой проблемы, но кроме этого вы устраните дублирование данных, что также дает гарантию того, что у вас не может быть начальной / конечной точки, не являющейся частью линейной строки. Недостатком этого является то, что вы не сможете иметь начальные / конечные точки в середине отрезка, но они должны находиться на одном из углов пути. Теперь вы должны определить, является ли это ограничение приемлемым в вашем приложении.

Если вы выберете приведенное выше представление, мы могли бы решить эту проблему с помощью следующей рекурсивной функции, где @path является линией, представляющей дорогу, @start_point а также @end_end представляют индексы двух точек на @path (первый индекс равен 1), и @test_point это точка географии, которая будет проверена. Контрольная точка может лежать где угодно на лжи.

CREATE FUNCTION [dbo].[func_PointBetween](@path        geography, 
                                          @start_point int, 
                                          @end_point   int,
                                          @test_point  geography)   
RETURNS tinyint
AS
BEGIN
    DECLARE @result       tinyint = 0;
    DECLARE @num_points   int = @path.STNumPoints();
    DECLARE @line_segment geography;

    IF (@start_point < @end_point) AND (@end_point < @num_points)
    BEGIN
        /* Generate the line segment from the current start point
           to the following point (@start_point + 1). */

        SET @line_segment = geography::STLineFromText('LINESTRING(' + 
            CAST(@path.STPointN(@start_point).Long AS varchar(32))+ ' ' + 
            CAST(@path.STPointN(@start_point).Lat AS varchar(32)) + ',' +
            CAST(@path.STPointN(@start_point + 1).Long AS varchar(32))+ ' ' + 
            CAST(@path.STPointN(@start_point + 1).Lat AS varchar(32)) + ')', 
            4326);

        /* Add a buffer of 25m to @test_point. This is optional, but 
           recommended, otherwise it will be very difficult to get a
           point exactly on the line. The buffer value may be tweaked
           as necessary for your application. */

        IF @test_point.STBuffer(25).STIntersects(@line_segment) = 1
        BEGIN
            /* The test point is on one of the line segments between
               @start_point and @end_point. Return 1 and stop the 
               recursion. */

            SET @result = 1;
        END
        ELSE
        BEGIN
            /* The test point is not between the @start_point and
               @start_point + 1. Increment @start_point by 1 and
               continue recursively. */

            SET @result = [dbo].[func_PointBetween](@path, 
                                                    @start_point + 1,
                                                    @end_point,
                                                    @test_point);
        END
    END
    ELSE
    BEGIN
        /* There are no further points. The test point is not between the
           @start_point and @end_point. Return 0 and stop the recursion. */

        SET @result = 0;
    END

    RETURN @result;
END

Чтобы проверить вышеупомянутую функцию, я определяю 6-точечную линейную линию, которая показана на карте выше. Затем мы определим две контрольные точки: @test_point_a, который лежит точно между третьей и четвертой точкой, и @test_point_b, который лежит вне пути.

DECLARE @road geography;
DECLARE @test_point_a geography;
DECLARE @test_point_b geography;

SET @road = geography::STGeomFromText('LINESTRING(-122.360 47.656, 
                                                  -122.343 47.656, 
                                                  -122.310 47.690, 
                                                  -122.310 47.670, 
                                                  -122.300 47.670, 
                                                  -122.290 47.660)', 
                                                  4326);

/* This point lies between point 3 and point 4 */           
SET @test_point_a = geography::STGeomFromText('POINT(-122.310 47.680)', 4326);

/* This point lies outside the path */
SET @test_point_b = geography::STGeomFromText('POINT(-122.310 47.700)', 4326);

/* This returns 1, because the test point is between start and end */
SELECT dbo.func_PointBetween(@road, 2, 5, @test_point_a);

/* This returns 0 because the test point is not between start and end */
SELECT dbo.func_PointBetween(@road, 4, 5, @test_point_a);

/* This returns 0 because the test point lies outside the path */
SELECT dbo.func_PointBetween(@road, 1, 6, @test_point_b);

Вы должны быть в состоянии проверить расстояние (STDistance) между новой точкой и подмножеством отрезка линии между начальной и конечной точками. Это расстояние должно оцениваться до нуля. Если у меня будет возможность углубиться в типы географических данных, я постараюсь составить точный запрос, но, надеюсь, это поможет вам начать работу.

Другие вопросы по тегам