Это немного сложно, но, безусловно, возможно.
Давайте начнем с расчета подшипника от одной точки к другой.Учитывая начальную точку, направление и расстояние, следующая функция вернет точку назначения:
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
Я понимаю, что вам требуется функция, которая принимает линейную строку в качестве входных данных, а не только начальную и конечную точки,Точка должна двигаться по пути соединенных отрезков и должна продолжать двигаться вокруг «углов» пути.Поначалу это может показаться сложным, но я думаю, что его можно решить следующим образом:
- Итерируйте по каждой точке вашей строки с помощью
STPointN()
, от x = 1 до x= STNumPoints()
. - Найти расстояние с
STDistance()
между текущей точкой итерации и следующей точкой: @linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
Если указанное выше расстояние> ваше входное расстояние 'n':
..., то точка назначения находится между этой точкой и следующей.Просто примените func_MoveTowardsPoint
точку прохода x в качестве начальной точки, точку x + 1 в качестве конечной точки и расстояние n.Вернуть результат и прервать итерацию.
Иначе:
... точка назначения находится дальше на пути от следующей точки в итерации.Вычтите расстояние между точкой x и точкой x + 1 из вашего расстояния n.Продолжайте итерацию с измененным расстоянием.
Возможно, вы заметили, что мы можем легко реализовать вышеизложенное, а не итеративно.
Давайте сделаем это:
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
С этим на месте, пришло время сделать несколько тестов.Давайте используем исходную строку, которая была предоставлена в вопросе, и мы запросим пункты назначения на 350 м, на 3500 м и на 7000 м:
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();
Наш тест возвращает следующие результаты:
POINT (-122.3553270591861 47.6560002502638)
POINT (-122.32676470116748 47.672728464582583)
POINT (-122.31 47.69)
Обратите внимание, что последнее запрошенное нами расстояние (7000 м) превысило длину линии линии, поэтому нам вернули последнюю точку.В этом случае вы можете легко изменить функцию, чтобы она возвращала NULL, если вы предпочитаете.