确定一个 POINT 是不是位于 LINESTRING 上的两个其他 POINT 之间(SQL Server 2008 Geography)

Posted

技术标签:

【中文标题】确定一个 POINT 是不是位于 LINESTRING 上的两个其他 POINT 之间(SQL Server 2008 Geography)【英文标题】:Determine if a POINT is between two other POINTs on a LINESTRING (SQL Server 2008 Geography)确定一个 POINT 是否位于 LINESTRING 上的两个其他 POINT 之间(SQL Server 2008 Geography) 【发布时间】:2010-01-27 22:00:00 【问题描述】:

我的数据库中有一个 SQL Server 2008 GEOGRAPHY 数据类型,其中包含 LINESTRINGLINESTRING 描述了一条可能弯曲的道路。

我有另一个表,其中包含一个起点和终点,都是GEOGRAPHY POINT's,在路上。我需要知道道路上这两个点之间是否有第三个点 (LINESTRING)。

目前,我正在测试:

第三个点在线上 新点到起点的距离和新点到终点的距离都小于起点和终点的距离

这个行得通,但如果道路自动掉头,它似乎真的很不雅根本行不通!有没有可行的方法?

【问题讨论】:

如果道路自行弯曲,您当前的方法是否有效?例如,如果您在 U 形转弯的每一端都有两个点,并且您在 U 形转弯后检查一个点,那么该点与起点和终点之间的距离可能小于起点和终点本身之间的距离,但它不会出现在两点之间的道路上。 哎呀,你是对的!好的,现在我真的需要一个答案。 :-) 【参考方案1】:

正如您所指出的,您的方法将在以下情况下失败,其中 S 是起点,E 是终点,X 是您正在测试的点:

Determine if a POINT is between two other POINTs on a LINESTRING http://img10.imageshack.us/img10/4937/gmap.png

使用该方法,点 X 将错误地导致位于点 S 和点 E 之间,因为它通过了算法的测试 1 和测试 2:即。点 X 在线串上,X 到 S 和 X 到 E 的距离都小于 S 到 E 的距离。


一种可能的解决方案

您可以将线串路径“分解”成单独的线段,每个线段只有两个点,这样:

LINESTRING(-122.360 47.656, -122.343 47.656, -122.310 47.690, -122.310 47.670)

会分解成:

LINESTRING(-122.360 47.656, -122.343 47.656)
LINESTRING(-122.343 47.656, -122.310 47.690)
LINESTRING(-122.310 47.690, -122.310 47.670)

然后您将能够遍历上述每个线段,并使用STIntersects 测试该点是否位于其中一个线段上。当一个点通过此测试时,您将能够确定它是否在起点和终点之间。

如果可能,我建议将您的起点/终点存储为线串路径上某个点的索引,而不是原始地理点。首先,这将使解决这个问题变得更容易,但除此之外,您将消除数据的重复,这还可以保证您不能有一个不属于线串的起点/终点。这样做的缺点是您将无法在线段的中间设置起点/终点,但它们必须位于路径的拐角处。现在您必须确定您的应用程序是否可以接受此限制。

如果你选择上面的表示,我们可以用下面的递归函数来解决这个问题,其中@path是代表道路的线串,@start_point@end_end代表@987654328上两个点的索引@(第一个索引是 1),@test_point 是要测试的地理点。测试点可以位于任何位置。

CREATE FUNCTION [dbo].[func_PointBetween](@path        geography, 
                                          @start_point int, 
                                          @end_point   int,
                                          @test_point  geography)   
RETURNS tinyint
AS
BEGIN
    DECLARE @result       tinyint = 0;
    DECLARE @num_points   int = @path.STNumPoints();
    DECLARE @line_segment geography;

    IF (@start_point < @end_point) AND (@end_point < @num_points)
    BEGIN
        /* Generate the line segment from the current start point
           to the following point (@start_point + 1). */

        SET @line_segment = geography::STLineFromText('LINESTRING(' + 
            CAST(@path.STPointN(@start_point).Long AS varchar(32))+ ' ' + 
            CAST(@path.STPointN(@start_point).Lat AS varchar(32)) + ',' +
            CAST(@path.STPointN(@start_point + 1).Long AS varchar(32))+ ' ' + 
            CAST(@path.STPointN(@start_point + 1).Lat AS varchar(32)) + ')', 
            4326);

        /* Add a buffer of 25m to @test_point. This is optional, but 
           recommended, otherwise it will be very difficult to get a
           point exactly on the line. The buffer value may be tweaked
           as necessary for your application. */

        IF @test_point.STBuffer(25).STIntersects(@line_segment) = 1
        BEGIN
            /* The test point is on one of the line segments between
               @start_point and @end_point. Return 1 and stop the 
               recursion. */

            SET @result = 1;
        END
        ELSE
        BEGIN
            /* The test point is not between the @start_point and
               @start_point + 1. Increment @start_point by 1 and
               continue recursively. */

            SET @result = [dbo].[func_PointBetween](@path, 
                                                    @start_point + 1,
                                                    @end_point,
                                                    @test_point);
        END
    END
    ELSE
    BEGIN
        /* There are no further points. The test point is not between the
           @start_point and @end_point. Return 0 and stop the recursion. */

        SET @result = 0;
    END

    RETURN @result;
END

为了测试上述功能,我定义了上图中所示的 6 点线串。然后我们将定义两个测试点:@test_point_a,恰好位于第三和第四点之间,@test_point_b,位于路径之外。

DECLARE @road geography;
DECLARE @test_point_a geography;
DECLARE @test_point_b geography;

SET @road = geography::STGeomFromText('LINESTRING(-122.360 47.656, 
                                                  -122.343 47.656, 
                                                  -122.310 47.690, 
                                                  -122.310 47.670, 
                                                  -122.300 47.670, 
                                                  -122.290 47.660)', 
                                                  4326);

/* This point lies between point 3 and point 4 */           
SET @test_point_a = geography::STGeomFromText('POINT(-122.310 47.680)', 4326);

/* This point lies outside the path */
SET @test_point_b = geography::STGeomFromText('POINT(-122.310 47.700)', 4326);

/* This returns 1, because the test point is between start and end */
SELECT dbo.func_PointBetween(@road, 2, 5, @test_point_a);

/* This returns 0 because the test point is not between start and end */
SELECT dbo.func_PointBetween(@road, 4, 5, @test_point_a);

/* This returns 0 because the test point lies outside the path */
SELECT dbo.func_PointBetween(@road, 1, 6, @test_point_b);

【讨论】:

【参考方案2】:

您应该能够检查新点与起点和终点之间的线段子集之间的距离 (STDistance)。该距离应评估为零。如果我有机会进一步深入研究地理数据类型,我会尝试将确切的查询放在一起,但希望这能让你开始。

【讨论】:

你当然是对的,但我找不到创建线段子集的方法。

以上是关于确定一个 POINT 是不是位于 LINESTRING 上的两个其他 POINT 之间(SQL Server 2008 Geography)的主要内容,如果未能解决你的问题,请参考以下文章

affine_trans_pixel 和 affine_trans_point_2d的区别

确定一个点是不是位于传单多边形内

确定一个点是不是位于任意形状内?

Python 3.X |确定坐标是不是位于矩形内

Android - 如何确定坐标是不是位于谷歌地图中的道路上

如何以编程方式确定文件是不是位于 Linux 和/或 macOS 的网络文件系统(NFS 或 SMB)上?