Как вы заметили, ваш метод потерпит неудачу в следующем случае, где 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
- индексы двух точек на * 1027. * (первый индекс - 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);