Điều này là một chút khó khăn, nhưng nó chắc chắn là có thể.
Hãy bắt đầu bằng cách tính toán vòng bi từ điểm này đến điểm khác. Với một điểm bắt đầu, một trục và một khoảng cách, hàm sau sẽ trả về điểm đích:
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
Tôi hiểu rằng bạn yêu cầu một hàm lấy chuỗi dòng làm đầu vào, không chỉ điểm bắt đầu và điểm kết thúc. Điểm phải di chuyển dọc theo một đường gồm các đoạn thẳng nối và phải tiếp tục di chuyển xung quanh các "góc" của đường dẫn. Điều này thoạt nghe có vẻ phức tạp, nhưng tôi nghĩ có thể giải quyết nó như sau:
- Lặp lại từng điểm trong chuỗi dòng của bạn với
STPointN()
, từ x =1 đến x =STNumPoints()
. - Tìm khoảng cách bằng
STDistance()
giữa điểm hiện tại trong lần lặp đến điểm tiếp theo:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
-
Nếu khoảng cách trên> khoảng cách đầu vào của bạn 'n':
... thì điểm đích nằm giữa điểm này và điểm tiếp theo. Chỉ cần áp dụng
func_MoveTowardsPoint
đi qua điểm x làm điểm bắt đầu, điểm x + 1 làm điểm cuối và khoảng cách n. Trả về kết quả và ngắt lặp lại.Khác:
... điểm đích nằm xa hơn trong đường dẫn từ điểm tiếp theo trong lần lặp. Trừ khoảng cách giữa điểm x và điểm x + 1 với khoảng cách 'n' của bạn. Tiếp tục lặp lại với khoảng cách đã sửa đổi.
Bạn có thể nhận thấy rằng chúng ta có thể dễ dàng thực hiện các điều trên một cách đệ quy, thay vì lặp đi lặp lại.
Hãy làm điều đó:
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
Với điều đó, đã đến lúc thực hiện một số bài kiểm tra. Hãy sử dụng chuỗi dòng ban đầu được cung cấp trong câu hỏi và chúng tôi sẽ yêu cầu các điểm đích ở độ cao 350m, 3500m và 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();
Thử nghiệm của chúng tôi trả về các kết quả sau:
POINT (-122.3553270591861 47.6560002502638)
POINT (-122.32676470116748 47.672728464582583)
POINT (-122.31 47.69)
Lưu ý rằng khoảng cách cuối cùng mà chúng tôi yêu cầu (7000m) đã vượt quá độ dài của chuỗi dòng, vì vậy chúng tôi đã được trả về điểm cuối cùng. Trong trường hợp này, bạn có thể dễ dàng sửa đổi hàm để trả về NULL, nếu muốn.