这个有点棘手,但肯定是可以做到的。
让我们从计算一个点到另一个点的方位角开始。给定起始点、方位角和距离,下面的函数将返回目标点:
CREATE FUNCTION [dbo].[func_MoveTowardsPoint](@start_point geography,
@end_point geography,
@distance int)
RETURNS geography
AS
BEGIN
DECLARE @ang_dist float = @distance / 6371000.0;
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;
SET @bearing = ATN2(sin(@lon_diff) * cos(@lat_2),
(cos(@lat_1) * sin(@lat_2)) -
(sin(@lat_1) * cos(@lat_2) *
cos(@lon_diff)));
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));
SET @new_lat = Degrees(@new_lat);
SET @new_lon = Degrees(@new_lon);
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':
...则目标点位于该点和下一个点之间。只需将点x作为起点、点x+1作为终点,距离n应用func_MoveTowardsPoint
。返回结果并中断迭代。
否则:
...目标点在迭代中下一个点的路径之后。从点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
SET @dist_to_next = @path.STPointN(@index).STDistance(@path.STPointN(@index + 1));
IF @distance <= @dist_to_next
BEGIN
SET @result = [dbo].[func_MoveTowardsPoint](@path.STPointN(@index),
@path.STPointN(@index + 1),
@distance);
END
ELSE
BEGIN
SET @result = [dbo].[func_MoveAlongPath](@path,
@distance - @dist_to_next,
@index + 1);
END
END
ELSE
BEGIN
SET @result = @path.STPointN(@index);
END
RETURN @result;
END
现在有了这个设置,该进行一些测试了。让我们使用在问题中提供的原始线串,并请求在350m、3500m和7000m处的目标点:
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。
@new_lon
时,我认为您想要在其中使用sin(@new_lat)
而不是sin(@lat_2)
。 - Bloopy