【问题标题】:Which algorithm can efficiently find a set of points within a certain distance of a path?哪种算法可以有效地找到路径一定距离内的一组点?
【发布时间】:2010-10-02 17:31:52
【问题描述】:

给定一组点 s(一组 x,y 坐标)和一条由连接一组点 l 的线段组成的路径,描述一种有效的算法,可用于从 s 中找到与路径 l 的指定距离 d 内的点的子集。 p>

此方法的一个实际应用可能是在城市之间的公路旅行路径上的任何地方查找 10 英里内的餐馆列表。

例如,在下图中,绿色的点将包含在搜索结果中。

解决方案在 C# 中是首选,但基于 SQL 的方法可能会获得奖励积分 :-)

【问题讨论】:

  • 图表?那是过度热心的复制粘贴还是图表正在路上? :)
  • 我对奖励积分感兴趣
  • SqlServer 2008 没有空间数据类型和操作符来操作它们吗?
  • 我不知道 SQLServer,但 PostGIS 对 PostgreSQL 的扩展肯定知道。
  • 使用 SQL08 空间的答案是否可以接受?这可以很容易地执行。

标签: algorithm geometry geospatial


【解决方案1】:

解决这个问题的唯一方法是:

for each point
  for each line
    is distance to line within constraints

一旦找到位于约束范围内的点,就可以提前终止内部循环。请注意,内循环和外循环可以调换。

然后问题就变成了确定一个点是否在约束范围内。 mbeckish 建议使用简单的矩形测试,其中矩形是通过沿线的垂线挤压形成的,但是对于端点附近但在该矩形之外的点,这将失败。沿线的方向拉伸矩形也会失败,因为靠近末端的点应该真正使用圆点测试:

|-------------
| *    /
|    --
|   /
|  /
| |
| |
|/
|         |--------| <- the line segment

* 位于扩展矩形内部但位于圆形端盖外部,这将是更严格的测试。

现在,距离测试可能不是“乌鸦飞”测试,而是图形搜索,例如,仅使用道路将它们连接在一起的道路 x 英里内的点:

--------------------------------------------------- < the road
   |
   |              * <- target
...|..............|................................ < check distance
   |              |
   |--------------| <- roads to target

在上图中,目标位于搜索区域内,但沿可用道路到达目标将大于允许的距离。

无论您选择如何实现测试,都需要基本的循环算法。

当约束是“as the crow flies”约束时检查约束的方法:

  1. 几何上:首先,确定点P到直线的距离。然后,如果该点在约束范围内,则将点 P 投影到线段上,其中线定义为:

    L = P1 + (P2-P1).n
    

    其中 P1 和 P2 是端点,n 是参数变量。如果投影 P 的 n 值在 0

  2. 变换:为每个线段创建一个变换矩阵,使 P1 变换为原点,P2 变换为 (|P1-P2|, 0)。然后将每个变换应用于所有点,然后测试矩形(0,-约束)-(|P1-P2|,约束)中的每个点。这种方法可以使用 SIMD 或 GPU 进行高度优化

  3. 以图形方式:使用带有圆形端盖且宽度与约束距离成比例的笔将线段绘制到位图。然后,对于每个测试点,检查该点对应的位图中的像素。这并不准确(但更大的位图会产生更准确的结果,但需要更多内存)但一旦创建位图就会很快。

    如果约束是由沿图的路线定义的,它会变得更加复杂。您需要查看广度优先搜索,其中起点是每条线段的终点,终点是潜在目标。如果一条线段沿其长度有交汇点,则将线段分成没有交汇点的线段。

【讨论】:

    【解决方案2】:

    1.) 使用几何数据类型(或地理,如果它们是使用纬度/经度坐标定义的)将您的点存储在 SQL Server 2008 表中 这是一个创建 100 个随机分布在 (0,0) 和 (40,20) 之间的样本点的脚本:

    DECLARE @Points table (
      id int,
      position geometry
      );
    
    DECLARE @i int = 0, @x int, @y int;
    WHILE (@i < 100)
    BEGIN
      INSERT INTO @Points VALUES 
        (@i, geometry::Point(RAND() * 40, RAND() * 20, 0))
      SET @i = @i + 1;
    END
    

    2.) 将您的线定义为线串,使用与您的点相同的数据类型和 SRID:

    DECLARE @line geometry = 'LINESTRING(0 10, 10 15, 20 8, 40 10)';
    

    3.) 在针对点表的 SELECT 查询的谓词中使用 STDistance() 方法。例如,要选择直线 5 个单位内的所有点:

    SELECT * FROM @Points
    WHERE @line.STDistance(position) < 5;
    

    此外,由于 SQL Server 的空间方法可在可再发行的 dll 中使用(Microsoft.SqlServer.Types.dll - SQL Server 功能包的一部分http://www.microsoft.com/downloads/en/details.aspx?FamilyID=ceb4346f-657f-4d28-83f5-aae0c5c83d52),您可以在 C# 或直接在 SQL Server 中。

    【讨论】:

      【解决方案3】:

      我很惊讶没有人为此提到 A* 算法。这似乎是一个完美的契合。我在这里想念什么?如果你不熟悉它,谷歌,你会发现=)。 (是的,它确实来自游戏世界……)

      【讨论】:

        【解决方案4】:

        鉴于通用计算工具,您最好的算法将是过滤掉明显不感兴趣的点并找到从每个线段到每个剩余点的距离的一些变化。 (建议的多边形解决方案是错误的——感兴趣的区域是该多边形与 l 上每个点周围半径为 d 的圆的并集——实际上效率较低而不是简单地找到从每个点到每个线段的距离。)

        哪些过滤器最好取决于数据的性质——例如,在示例图中,过滤 l 的边界框(加上 d)会很有用的。

        一个有趣的过滤器是:给定点 p 定义 l,取一个半径为 r 的圆,其中 r em> 是由 p 加上 d 部分定义的两个段的最大长度。只有这个圆内的点才能与这两个线段足够接近才能在我们的解决方案集中,因此我们可以快速确定是否可以跳过这两个线段距离计算。 (如果某些线段很长,这会降低效率,但如果它们很长,这些线段可以很容易地分解成更小的块。)

        【讨论】:

          【解决方案5】:

          如果您想至少在 SQL 中完成一些工作,您可以计算路径的边界框,然后将位置在边界框内的条件合并到您的查询中。您只针对返回的行运行其他算法之一。

          这至少使您不必为每个路径下载整个数据库。

          【讨论】:

            【解决方案6】:

            我相信这两个课程会回答你的问题。我使用Heron's Formula 构建了GetArea() 函数。确保段点始终首先传递给 IsPointWithinDistanceToLineSegment,而 TestPoint 始终第三传递。

            编辑:我愚蠢地使用了 Point,它只允许 X 和 Y 使用整数。你需要使用另一个将双精度或浮点数作为 X 和 Y 的类来解决这个问题......

            public class Geometry
            {
                public static double GetDistanceBetweenTwoPoints(Point SegmentStart, Point SegmentEnd)
                {
                    return Math.Sqrt(Math.Pow(SegmentEnd.X - SegmentStart.X, 2) + Math.Pow(SegmentEnd.Y - SegmentStart.Y, 2));
                }
            
                public static bool IsPointWithinDistanceToLineSegment(Point SegmentStart, Point SegmentEnd, Point TestPoint, double TestDistance)
                {
                    if (GetDistanceBetweenTwoPoints(SegmentStart,SegmentEnd) <= TestDistance || GetDistanceBetweenTwoPoints(SegmentEnd,TestPoint) <= TestDistance)
                    {
                        return true;
                    }
                    var T = new Triangle(SegmentStart, SegmentEnd, TestPoint);
                    var BaseLength = GetDistanceBetweenTwoPoints(SegmentStart, SegmentEnd);
                    var Area = T.GetArea();
                    var TriangleHeight = 2* Area / BaseLength;
                    return T.AB >= T.BC && T.AB >= T.AC && TriangleHeight <= TestDistance;
                }
            }
            
            public class Triangle
            {
                public Triangle(Point a, Point b, Point c)
                {
                    this.a = a;
                    this.b = b;
                    this.c = c;
                }
            
                public Point a
                {
                    get;
                    set;
                }
            
                public Point b
                {
                    get;
                    set;
                }
            
                public Point c
                {
                    get;
                    set;
                }
            
                //Lengths of Sides
                public double AB
                {
                    get
                    {
                        return Geometry.GetDistanceBetweenTwoPoints(a, b);
                    }
                }
            
                public double AC
                {
                    get
                    {
                        return Geometry.GetDistanceBetweenTwoPoints(a, c);
                    }
                }
            
                public double BC
                {
                    get
                    {
                        return Geometry.GetDistanceBetweenTwoPoints(b, c);
                    }
                }
            
                public double GetArea()
                {
                    var Term1 = Math.Pow((Math.Pow(AB, 2) + Math.Pow(AC, 2) + Math.Pow(BC, 2)), 2);
                    var Term2 = 2 * (Math.Pow(AB, 4) + Math.Pow(AC, 4) + Math.Pow(BC, 4));
                    var result = .25 * Math.Sqrt(Term1 - Term2);
                    return result;
                }
            }
            

            【讨论】:

              【解决方案7】:

              您应该能够通过向量数学和触发来完成此操作,尽管我无法理解确切的方法。

              对于每个线段,计算需要将点从世界坐标转换为相对于线段的局部坐标的值(因此通过计算运行的任何点都将相对于线段为 x 轴的坐标系)

              对每个点运行以下检查:

              1- 如果该点在任一端点的距离内,我们知道它应该被包括在内。这是通过每个端点和目标点之间的简单距离^2

              2- 通过变换运行目标点。改造后如果 x >= 0 和 x

              我的向量数学有点生疏,所以我无法提供更好的代码/示例对不起!但也许我的帖子会启发其他人写出正确的方法。

              【讨论】:

                【解决方案8】:

                艰巨的家庭作业是吗?

                也许一个好的开始可能是研究广度优先寻路算法——也许像洪水填充方法这样的东西对此有用?

                编辑:所以如果它只是看起来像家庭作业,也许我可以提供更多帮助......

                我会首先定义一个包含直线和其中可能包含的点的矩形,因为这可以让我们摆脱大量远离我们的直线的点。

                然后,您可以为每个点创建一个正方形,表示该点半径内的点列表。这又是一种减少要搜索的元素数量的方法。

                不幸的是,除了简单地通过基本触发计算它们与圆心之间的距离之外,我还没有足够的几何知识来了解确定点列表是落在圆内还是圆外的聪明方法-我确定有一个。通过使用前面提到的简单细分或一些变体,您应该会发现您可以先发制人地减少需要搜索的可能点的数量。

                此外,如果您将所有要搜索的点保留在一个列表中,并在测量后续形状时删除第一个圆的命中点。我已经使用了这个的蛮力版本来根据位置数据进行简单的邮政编码距离检查 - 这在网上很多地方都有记录,但是沿着一条路径运行它可能会在计算上非常昂贵。

                这种几何方法可能更适合您没有进行大量重复搜索的情况 - 如果连续有很多,您可能希望将您的 ponts 组织成一个网络,以便您可以使用标准寻路他们。值得做一些原型设计来看看哪个更有效,但我希望如果您要创建一个合适的网络来表示您的数据,那么您可以更灵活地搜索它。

                【讨论】:

                • 大声笑...家庭作业...我希望。到目前为止,我已经探索了一些(低效且不准确的)解决方案,但我确信肯定有一种久经考验的算法——即使来自 2D 图形世界。
                • 啊,缺少图表使它看起来像是复制和粘贴的家庭作业,请考虑我已更正。当然,洪水填充类型的方法可以解决这个问题。
                【解决方案9】:

                我不确定我是否正确理解了这个问题,但Dijkstra's algorithm 不合适吗?它从源节点找到最短路径,您可以在达到最大距离后中止并检查 s 中的哪些点已经找到。不过,我不确定它与 SQL 配合得如何。

                【讨论】:

                  【解决方案10】:

                  前段时间我也想过这个问题。我认为,高效是一种误导。只需测试每个点的所有线段就足够了。它是very cheap to calculate the distance。 如果有很多点,你也可以考虑使用水平集的方法来细化选择哪些点的策略。即

                  • 沿着线走,步幅是您要检查的距离的 2 倍(或多或少?),并创建“接近”的人工点。
                  • itereate:在“近”点周围选择新点(不要计算欧几里德距离,只计算 1 范数并简单地测试 x 和 y 坐标) - 然后测试它们的距离(您甚至可以继承特定的从人工点到找到的“近”点的线段,并首先选择那个进行测试,但要扩大搜索范围,因为可能会有曲折!)

                  这可能不完整,但应该很快,避免检查点很远,很好。

                  【讨论】:

                  • 当您意识到您可以使用距离的平方并避免平方根计算时,它会更便宜。
                  【解决方案11】:

                  您能否使用四叉树将空间划分为多个段,然后仅针对靠近您路径的段中的点?

                  【讨论】:

                    【解决方案12】:
                    1. 定义一个“左路”和“右路”:对于原始路径的每条线段,在该线段的“左”和“右”分别创建一个 d 个单位和一个 d 个单位的线段.

                    2. 将两端的左路和右路连接起来,形成一个多边形。

                    3. 应用标准算法来确定多边形内的兴趣点。

                    【讨论】:

                    • 这不涉及路径起点和终点的 10 英里...它缺少末端的半圆形帽...
                    • 另一方面,找出哪些点在(比如说)两点的十英里之内是相当快的。其中一部分处理多边形,另一部分处理圆形,这两者可能应该分开处理。
                    • 多边形的生成本身就是一项任务......“左”和“右”类比只适用于垂直线......而不是左右,需要绘制平行线对于每个线段,然后连接以形成多边形,确保关节不会意外排除点。
                    • 多边形应该很容易生成。对于点 P1=(x1, y1) 和 P2=(x2, y2),要添加的增量为 D=d*[y1-y2, x2-x1]/|P2-P1|,给出点 (P1+ D), (P2+D), (P2-D), (P1-D)。
                    猜你喜欢
                    • 1970-01-01
                    • 2017-05-28
                    • 1970-01-01
                    • 2015-12-21
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-04-10
                    • 1970-01-01
                    相关资源
                    最近更新 更多