C'est un peu délicat, mais c'est certainement possible.
Commençons par calculer le relèvement d'un point à un autre. Étant donné un point de départ, un relèvement et une distance, la fonction suivante renverra le point de destination :
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
Je comprends que vous avez besoin d'une fonction qui prend une chaîne de lignes en entrée, pas seulement des points de début et de fin. Le point doit se déplacer le long d'un chemin de segments de ligne concaténés et doit continuer à se déplacer autour des "coins" du chemin. Cela peut sembler compliqué au premier abord, mais je pense qu'il peut être abordé comme suit :
- Parcourez chaque point de votre chaîne de lignes avec
STPointN()
, de x=1 à x=STNumPoints()
. - Trouvez la distance avec
STDistance()
entre le point actuel de l'itération et le point suivant :@linestring.STPointN(x).STDistance(@linestring.STPointN(x+1))
-
Si la distance ci-dessus> votre distance d'entrée 'n' :
...alors le point de destination se situe entre ce point et le suivant. Appliquez simplement
func_MoveTowardsPoint
passant le point x comme point de départ, le point x+1 comme point final et la distance n. Renvoie le résultat et interrompt l'itération.Sinon :
... le point de destination est plus loin dans le chemin du point suivant dans l'itération. Soustrayez la distance entre le point x et le point x+1 de votre distance 'n'. Continuez l'itération avec la distance modifiée.
Vous avez peut-être remarqué que nous pouvons facilement implémenter ce qui précède de manière récursive, au lieu de manière itérative.
Allons-y :
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
Avec cela en place, il est temps de faire quelques tests. Utilisons la chaîne de lignes d'origine fournie dans la question, et nous demanderons les points de destination à 350 m, à 3 500 m et à 7 000 m :
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();
Notre test renvoie les résultats suivants :
POINT (-122.3553270591861 47.6560002502638)
POINT (-122.32676470116748 47.672728464582583)
POINT (-122.31 47.69)
Notez que la dernière distance que nous avons demandée (7000 m) dépassait la longueur de la ligne, nous avons donc renvoyé le dernier point. Dans ce cas, vous pouvez facilement modifier la fonction pour renvoyer NULL, si vous préférez.