我的数据库中存储了一个地理字段,其中包含一个线串路径。
我想沿着这个线串移动一个点n
米,然后返回目的地。
例如,我希望目标点从线条开始沿着线串500米。
以下是一个例子 - YourFunctionHere
是什么?或者,还有另一种方式吗?
DECLARE @g geography;
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656, -122.310 47.690)', 4326);
SELECT @g.YourFunctionHere(100).ToString();
答案 0 :(得分:13)
这有点棘手,但肯定有可能。
让我们从一个点到另一个点计算轴承。给定起点,方位和距离,以下函数将返回目标点:
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。返回结果并打破迭代。
否则:
...目标点位于迭代中下一个点的路径中。从距离'n'中减去点x和点x + 1之间的距离。继续修改距离的迭代。
您可能已经注意到我们可以轻松地递归地实现上述内容,而不是迭代地实现上述内容。
我们这样做:
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。
答案 1 :(得分:4)
CodePlex上的SQL Spatial工具库中还有LocateAlongGeog函数 http://sqlspatialtools.codeplex.com/wikipage?title=Current%20Contents&referringTitle=Home
答案 2 :(得分:1)
我使用了上面的Daniel的回答,但我必须修复“func_MoveAlongPath”签名
CREATE FUNCTION [dbo].[func_MoveAlongPath](@path geography,
@distance **float**,
@index int = 1)
int会返回错误的结果,因为它会舍入递归调用中的值。 然后我将其转换为迭代版本,因为递归版本无法处理我所拥有的样本数据中的更大距离:
CREATE FUNCTION [dbo].[func_MoveAlongPathIter](@path geography,
@distance float)
RETURNS geography
AS
BEGIN
DECLARE @index int = 1;
DECLARE @result geography = null;
DECLARE @num_points int = @path.STNumPoints();
DECLARE @dist_to_next float;
DECLARE @comul_distance float = 0;
WHILE (@index < @num_points - 1) AND (@comul_distance < @distance)
BEGIN
SET @dist_to_next = @path.STPointN(@index).STDistance(@path.STPointN(@index + 1));
SET @comul_distance += @dist_to_next;
SET @index += 1;
END
SET @result = [dbo].[func_MoveTowardsPoint](@path.STPointN(@index - 1),
@path.STPointN(@index),
@distance - (@comul_distance - @dist_to_next));
RETURN @result;
END