Определите, находится ли ТОЧКА между двумя другими ТОЧКАМИ НА СТРОКЕ (география SQL Server 2008)
У меня SQL Server 2008 GEOGRAPHY
тип данных в моей базе данных, содержащей LINESTRING
, LINESTRING
описывает потенциально извилистую дорогу.
У меня есть другая таблица, которая содержит начальную и конечную точки, оба GEOGRAPHY
POINT
на дороге. Мне нужно знать, если третья точка попадает между этими двумя точками на дороге (LINESTRING
).
В настоящее время я тестирую это:
- третья точка на линии
- расстояние между новой точкой до начальной точки и расстояние между новой точкой и конечной точкой меньше расстояния между начальной точкой и конечной точкой
Это работает, но кажется, что действительно не элегантно не работает вообще, если дорога разворачивается сама собой! Есть ли способ, который работает?
2 ответа
Как вы заметили, ваш метод завершится ошибкой в следующем случае, где S - начальная точка, E - конечная точка, а X - точка, которую вы тестируете:
Используя этот метод, точка 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) между новой точкой и подмножеством отрезка линии между начальной и конечной точками. Это расстояние должно оцениваться до нуля. Если у меня будет возможность углубиться в типы географических данных, я постараюсь составить точный запрос, но, надеюсь, это поможет вам начать работу.