Ini sedikit rumit, tetapi tentu saja mungkin.
Mari kita mulai dengan menghitung bantalan dari satu titik ke titik lainnya. Diberikan titik awal, bantalan, dan jarak, fungsi berikut akan mengembalikan titik tujuan:
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
Saya mengerti bahwa Anda memerlukan fungsi yang mengambil linestring sebagai input, bukan hanya titik awal dan akhir. Titik tersebut harus bergerak di sepanjang jalur segmen garis yang digabungkan, dan harus terus bergerak di sekitar "sudut" jalur tersebut. Ini mungkin tampak rumit pada awalnya, tetapi saya pikir ini dapat diatasi sebagai berikut:
- Ulangi setiap titik linestring Anda dengan
STPointN()
, dari x=1 hingga x=STNumPoints()
. - Temukan jarak dengan
STDistance()
antara titik saat ini dalam iterasi ke titik berikutnya:@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
-
Jika jarak di atas> jarak input Anda 'n':
...maka titik tujuan berada di antara titik ini dan titik berikutnya. Cukup terapkan
func_MoveTowardsPoint
melewati titik x sebagai titik awal, titik x+1 sebagai titik akhir, dan jarak n. Kembalikan hasilnya dan hentikan iterasi.Lainnya:
... titik tujuan lebih jauh di jalur dari titik berikutnya dalam iterasi. Kurangi jarak antara titik x dan titik x+1 dari jarak Anda 'n'. Lanjutkan melalui iterasi dengan jarak yang dimodifikasi.
Anda mungkin telah memperhatikan bahwa kami dapat dengan mudah mengimplementasikan hal di atas secara rekursif, bukan secara iteratif.
Ayo lakukan:
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
Dengan itu, saatnya untuk melakukan beberapa tes. Mari kita gunakan linestring asli yang disediakan dalam pertanyaan, dan kami akan meminta titik tujuan di 350m, di 3500m, dan di 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();
Pengujian kami mengembalikan hasil berikut:
POINT (-122.3553270591861 47.6560002502638)
POINT (-122.32676470116748 47.672728464582583)
POINT (-122.31 47.69)
Perhatikan bahwa jarak terakhir yang kami minta (7000m) melebihi panjang tali garis, jadi kami mengembalikan poin terakhir. Dalam hal ini, Anda dapat dengan mudah memodifikasi fungsi untuk mengembalikan NULL, jika Anda mau.