确定某个点是否位于 LINESTRING 上的其他两个点之间(SQL Server 2008 地理)

发布于 2024-08-19 05:44:05 字数 405 浏览 14 评论 0原文

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

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

目前我正在测试:

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

这个可以工作,但看起来真的很不优雅如果道路本身掉头就根本不起作用!有没有有效的方法?

I have a SQL Server 2008 GEOGRAPHY data type in my database containing a LINESTRING. The LINESTRING describes a potentially curvy road.

I have another table that contains a start point and end point, both GEOGRAPHY POINT's, on the road. I need to know if a third point falls between those two points on the road (LINESTRING).

Currently, I'm testing that:

  • the third point is on the line
  • the distance between the new point to the start point and the distance between the new point and the end point are both less than the distance between the start point and end point

This works, but it seems really inelegant doesn't work at all if the road u-turns on itself! Is there a way that works?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

隱形的亼 2024-08-26 05:44:05

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

确定某个点是否位于 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 表示@path 上两个点的索引(第一个索引为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);

As you have noted, your method will fail in the following case, where S is the start point, E is the end point, and X is the point you are testing:

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

Using that method, point X will falsely result as being between point S and point E, because it passes both test 1 and test 2 of your algorithm: ie. Point X is on the linestring, and the distances from X to S and from X to E are both smaller than the distance from S to E.


One Possible Solution

You can "explode" your linestring path into separate line segmenets with just two points each, so that:

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

would get broken down into:

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)

Then you would be able to iterate through each of the above line segments and test if the point lies on one of these segments using STIntersects. When a point passes this test, you would be able to determine if this was within the start point and the end point.

If possible, I would suggest storing your start/end points as an index to a point on your linestring path, instead of a raw geography point. First of all, this will make it easier to solve this problem, but apart from that, you would eliminate the duplication of data, which also comes with a guarantee where you cannot have a start/end point that is not part of a linestring. The disadvantage of this is that you will not able to have start/end points in the middle of a line segment, but they have to be on one of the corners of the path. Now you have to determine if this limitation is acceptable in your application.

If you opt for the above representation, we could solve this problem with the following recursive function, where @path is the linestring representing the road, @start_point and @end_end represent the indexes of two points on the @path (first index is 1), and @test_point is the geography point that will be tested. The test point can lie anywhere on the lie.

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

To test the above function, I am defining the 6-point linestring which is shown in the map above. Then we'll define two test points: @test_point_a, which lies exactly between the third and the fourth point, and @test_point_b, which lies out of the path.

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);
つ低調成傷 2024-08-26 05:44:05

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

You should be able to check the distance (STDistance) between the new point and the subset of the line segment between the start and end points. That distance should evaluate to zero. If I have a chance to delve further into the geography data types I'll try to put together the exact query, but hopefully this gets you started.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文