【问题标题】:SQL Geometry find all points in a radiusSQL几何查找半径中的所有点
【发布时间】:2014-03-07 08:53:38
【问题描述】:

我精通 SQL,但不熟悉使用 SQL 几何功能。我有一个可能是一个非常基本的问题要解决,但我还没有在网上找到任何好的资源来解释如何使用几何对象。 (Technet 是一种糟糕的学习新事物的方式……)

我有一个笛卡尔平面上的二维点集合,我正在尝试查找半径集合内的所有点。

我使用如下语法创建并填充了一个表:

更新 [事物] 设置 [位置] = geometry::Point(@X, @Y, 0)

(@X,@Y 只是 x 和 y 值,0 是所有对象共享的任意数字,如果我理解正确,允许设置过滤)

这是我偏离轨道的地方......我是否尝试使用它构建某种多边形集合和查询,或者是否有一些简单的方法来检查多个半径的交集而不构建一堆圆形多边形?

附录:如果没有人知道多半径问题的答案,那么单半径解决方案是什么?

更新

以下是我处理的一些示例,使用虚构的星数据库,其中星作为点存储在 x-y 网格上:

选择框中的所有点:

DECLARE @polygon geometry = geometry::STGeomFromText('POLYGON((' 
+ CAST(@MinX AS VARCHAR(10)) + ' '  + CAST(@MinY AS VARCHAR(10)) + ',' 
+ CAST(@MaxX AS VARCHAR(10)) + ' '  + CAST(@MinY AS VARCHAR(10)) + ', ' 
+ CAST(@MaxX AS VARCHAR(10)) + ' '  + CAST(@MaxY AS VARCHAR(10)) + ',' 
+ CAST(@MinX AS VARCHAR(10)) + ' '  + CAST(@MaxY AS VARCHAR(10)) + ',' 
+ CAST(@MinX AS VARCHAR(10)) + ' '  + CAST(@MinY AS VARCHAR(10)) + '))', 0);

SELECT  [Star].[Name]           AS [StarName],
        [Star].[StarTypeId]     AS [StarTypeId],        
FROM    [Star]
WHERE   @polygon.STContains([Star].[Location]) = 1

以此为模式,你可以做各种有趣的事情,比如 定义多个多边形:

WHERE   @polygon1.STContains([Star].[Location]) = 1
OR @polygon2.STContains([Star].[Location]) = 1
OR @polygon3.STContains([Star].[Location]) = 1

或检查距离:

WHERE [Star].[Location].STDistance(@polygon1) < @SomeDistance 

示例插入语句

INSERT [Star]
(
    [Name],
    [StarTypeId],
    [Location],
)
VALUES
(
    @Name,
    @StarTypeId,
    GEOMETRY::Point(@LocationX, @LocationY, 0),
)

【问题讨论】:

    标签: sql spatial spatial-query


    【解决方案1】:

    当然,这是可能的。单独的 where 子句应该是这样的:

    DIM @Center AS Location
    -- Initialize the location here, you probably know better how to do that than I.
    Dim @Radius AS Decimal(10, 2)
    SELECT * from pointTable WHERE sqrt(square(@Center.STX-Location.STX)+square(@Center.STX-Location.STX)) > @Radius 
    

    然后,您可以将一堆半径和 xy 点堆积到一个表格变量中,如下所示:

    Dim @MyCircleTable AS Table(Geometry Circle) 
    INSERT INTO @MyCircleTable (.........)
    

    注意:我没有通过编译器进行编译,但这是一个可行的解决方案的基本框架。

    其他选项似乎在这里: http://technet.microsoft.com/en-us/library/bb933904.aspx

    这里有一个看似有效的语法演示: http://social.msdn.microsoft.com/Forums/sqlserver/en-US/6e1d7af4-ecc2-4d82-b069-f2517c3276c2/slow-spatial-predicates-stcontains-stintersects-stwithin-?forum=sqlspatial

    第二个帖子暗示语法:

    SELECT Distinct pointTable.* from pointTable pt, circletable crcs
    WHERE crcs.geom.STContains(b.Location) = 1
    

    【讨论】:

    • 如果您打算使用基本的 SQL 数据类型,您会这样做。但是,我特别询问是否使用 SQL 几何对象,因为它们使用四叉树或其他空间索引。如果您想搜索一个包含几十万个点的数据库,您的解决方案将完全崩溃,而使用空间分区的对象根本没有问题。当我添加额外的半径时,您的解决方案会变得更糟,因为我必须检查我要查看的每个半径到数据库中每个点的距离。不过还是感谢您的尝试
    • 服务器并不神奇,反正你会做大量的计算。如果这些是巨大的表并且服务器负载/正常运行时间很重要,您将需要一个数据仓库或计算字段。也就是说,解决方案已更新为包含 SQL-Native 可能性。
    • 这是不正确的。空间索引(BSP 树、四叉树等)的重点是通过快速将搜索空间修剪为相关数据来避免进行所有数学运算。空间索引,如果使用得当,速度非常非常快。话虽如此,您找到的链接非常有帮助。我将撰写并发布有关如何为未来的读者执行此操作的概要。感谢您的帮助。
    【解决方案2】:

    这是一个非常晚的答案,但也许我可以对解决方案有所了解。您所指的“集合”编号是空间参考标识符或 SRID。对于纬度/经度计算,您应该考虑将其设置为 4326,这将确保将米用作测量单位。您还应该考虑切换到 SqlGeography 而不是 SqlGeometry,但我们现在将继续使用 SqlGeometry。要批量设置 SRID,您可以按如下方式更新表:

    UPDATE [YourTable] SET [SpatialColumn] = GEOMETRY.STPointFromText([SpatialColumn].STAsText(), 4326);
    

    对于单个半径,您需要创建一个半径作为空间对象。例如:

    DECLARE @radiusInMeters FLOAT = 1000; -- Set to a number in meters
    DECLARE @radius GEOMETRY = GEOMETRY::Point(@x, @y, 4326).STBuffer(@radiusInMeters);
    

    STBuffer() 获取空间点并从中创建一个圆(现在是 Polygon 类型)。然后您可以按如下方式查询您的数据集:

    SELECT * FROM [YourTable] WHERE [SpatialColumn].STIntersects(@radius);
    

    以上内容现在将使用您在其查询计划中在 [SpatialColumn] 上创建的任何空间索引。

    还有一个更简单的选项可以工作(并且仍然使用空间索引)。 STDistance 方法允许您执行以下操作:

    DECLARE @radius GEOMETRY = GEOMETRY::Point(@x, @y, 4326);
    DECLARE @distance FLOAT = 1000; -- A distance in metres   
    SELECT * FROM [YourTable] WHERE [SpatialColumn].STDistance(@radius) <= @distance;
    

    最后,使用半径集合。你有几个选择。第一个是依次为每个半径运行上述操作,但我会考虑以下操作:

    DECLARE #radiiCollection TABLE
    (
        [RadiusInMetres] FLOAT,
        [Radius] GEOMETRY
    )
    
    INSERT INTO #radiiCollection ([RadiusInMetres], [Radius]) VALUES (1000, GEOMETRY::Point(@xValue, @yValue, 4326).STBuffer(1000));
    -- Repeat for other radii
    
    SELECT
        X.[Id],
        MIN(R.[RadiusInMetres]) AS [WithinRadiusDistance]
    FROM
        [YourTable] X
        JOIN
        #radiiCollection RC ON RC.[Radius].STIntersects(X.[SpatialColumn])
    GROUP BY
        X.[IdColumn],
        R.[RadiusInMetres]
    
    DROP TABLE @radiiCollection;
    

    上面的最终结果尚未经过测试,但我 99% 确信它就在那里,可能会进行少量调整。在选择中采用最小半径距离的理想情况是,如果多个半径源于一个位置,如果一个点在第一个半径内,它自然会在所有其他半径内。因此,您将复制记录,但通过分组然后选择最小值,您只会得到一个(也是最接近的)。

    希望对您有所帮助,尽管是在您提出问题 4 周后。抱歉没早点看到,要是问题只有一个空间标签就好了!!!!

    【讨论】:

    • 有趣的第二个解决方案,我需要尝试一下并考虑一下。两个小问题:我很确定,除非您在曲面上工作,否则您不想使用 GEOGRAPHY 4326,这是一个非欧几里得曲面。这是一个平面空间问题,因此使用 GEOMETRY 可能是正确的选择。其次,在您的示例中,您建议使用 sql 临时表,而使用表变量会更好(更快)。临时表写入磁盘,表变量在内存中。
    • @MadTigger 我完全同意使用表变量,但老实说,我没有花时间在优化上,更多的理论可以帮助你 - 鉴于有4周你可能已经解决了! :-) 再次原谅对 4326 的假设,如果您正在处理平面空间问题,那么不使用 4326 并使用几何是完全正确的。当人们应该使用地理时,我已经习惯了使用几何作为实际“地球”问题的逃避,而你的问题在这件事上并不清楚。假设习惯是我应该改掉的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 1970-01-01
    • 2020-04-25
    • 1970-01-01
    相关资源
    最近更新 更多