【问题标题】:Query set of points in AREA within distance from line segment查询距线段距离内的 AREA 中的点集
【发布时间】:2015-10-11 13:56:40
【问题描述】:

我将线段和点存储在数据库中。我将如何查询数据库以检索多个线段一定距离内的所有点。 目的是当用户点击一条路径(道路)时,所有在路径距离内的物体都应该被高亮显示。

谢谢。

更新: 例子... 我有一条从 (0,0) 到 (0, 10) 的路径。程序应找到并突出显示该路径 x 距离内的所有对象。 假设 x 距离为“2”……那么,程序应突出显示矩形 (0,2)(10,-2​​) 内的所有对象。基本上,这与查找所有靠近线的对象(不仅仅是单个点)相同。

当线是水平的时候很容易......但我不知道如何解决所有情况,包括线可能是斜率。

更新:这些点存储在一个大型数据库中,因此我无法检查每个点的接近度。我正在尝试找到一种方法来仅检索足够接近而不会过多重叠请求的那些...一旦检索到它们,我可以使用“点和线段之间的距离”中描述的方法来细化搜索”。 (我想!) 谢谢!

【问题讨论】:

  • 你有一些代码,或者数据如何存储的例子吗?
  • 数据库中每一行有3个字段:id, x_axis, y_axis..都是整数
  • 你想知道的一切似乎都在这里:stackoverflow.com/questions/849211/…
  • 您可以做出任何假设来简化数学运算吗?线段是否都具有相似的长度?用户可以输入的最大距离是否总是大于分段长度?路线或多或少是直的?

标签: javascript php sql database


【解决方案1】:

这将为您提供从点 p 到线段 v,w 的距离。 (基于这个问题:Shortest distance between a point and a line segment)。您必须遍历所有点并计算到所有线段的距离,才能找到离路线足够近的线段。
如果太慢,您将不得不进行某种不需要平方根的简化。

function distanceToLineSegment(p, v, w)
{
    var len2 = dist2(v, w);
    if (len2 == 0) return Math.sqrt(dist2(p, v));
    var s = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / len2;
    if (s < 0) return Math.sqrt(dist2(p, v));
    if (s > 1) return Math.sqrt(dist2(p, w));
    var i = {x: v.x + s * (w.x - v.x), y: v.y + s * (w.y - v.y)};
    return Math.sqrt(dist2(p, i));

    function dist2(p, q) {
        return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
    }
}

alert(distanceToLineSegment({x:2, y:3}, {x:-1, y:4}, {x:3, y:8}));

这是一个经过一定程度优化的实现,可以根据路线检查点列表。
要检查的点存储为具有 x 和 y 值以及 id 字符串的点的数组far[]。还有第二个最初为空的数组close[],如果发现点靠近路线,则将它们移动到其中,这样就不会对点进行两次检查。这两个数组存储在一个对象points中,这样它们就可以在函数之间通过引用传递,而不是不断地被复制。为了提高效率,我还删除了平方根函数。
通过将距离计算更改为更粗略的近似值(可能使用矩形)而不是数学上正确的近似值,可能会进一步优化。

function isCloseToRoute(points, route, distance) {
    var distance2 = Math.pow(distance, 2);
    for (var i = 0; i < route.length - 1; i++) {
        isCloseToLineSegment(points, route[i], route[i + 1], distance2);
    }

    function isCloseToLineSegment(points, v, w, distance2) {
        for (var i = points.far.length - 1; i >= 0; i--) {
            if (distanceToLineSegment2(points.far[i], v, w) <= distance2) {
                points.close.push(points.far.splice(i, 1)[0]);
            }
        }
    }

    function distanceToLineSegment2(p, v, w) {
        var len2 = dist2(v, w);
        if (len2 == 0) return dist2(p, v);
        var q = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / len2;
        if (q < 0) return dist2(p, v);
        if (q > 1) return dist2(p, w);
        var i = {x: v.x + q * (w.x - v.x), y: v.y + q * (w.y - v.y)};
        return dist2(p, i);
    
        function dist2(p, q) {
            return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
        }
    }
}

var points = {close: [], far: [{x: 1, y: 0, id: "A"}, 
                               {x: 2, y: 1, id: "B"}, 
                               {x:-1, y: 8, id: "C"}, 
                               {x:-3, y: 4, id: "D"}]};
var route = [{x: 0, y: 0}, {x: 1, y: 2}, {x:-1, y: 4}, {x: 2, y: 8}];

isCloseToRoute(points, route, 2);
alert(points.close.length + " points found near route");
for (i in points.close) console.log(points.close[i].id);

如果将地图划分为网格,则可以使用 isCloseToRoute() 检查路线附近的网格单元。它将返回具有“6,4”之类的键的网格单元列表;如果您为数据库中的每个点指定一个键,指示它位于哪个网格单元格中,您可以查找它们而无需对坐标进行任何数学运算。
就像检查点列表时一样创建一个输入对象,用网格单元的中心点填充 far[] 数组,然后在其上运行 isCloseToRoute(),距离为 (distance + gridSize*sqrt(2)/ 2).
在示例中,地图是一个 1000 x 1000 的正方形,分为 64 个网格单元,每个单元的大小为 125 x 125。

function isCloseToRoute(points, route, distance) {
    var distance2 = Math.pow(distance, 2);
    for (var i = 0; i < route.length - 1; i++) {
        isCloseToLineSegment(points, route[i], route[i + 1], distance2);
    }

    function isCloseToLineSegment(points, v, w, distance2) {
        for (var i = points.far.length - 1; i >= 0; i--) {
            if (distanceToLineSegment2(points.far[i], v, w) <= distance2) {
                points.close.push(points.far.splice(i, 1)[0]);
            }
        }
    }

    function distanceToLineSegment2(p, v, w) {
        var len2 = dist2(v, w);
        if (len2 == 0) return dist2(p, v);
        var q = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / len2;
        if (q < 0) return dist2(p, v);
        if (q > 1) return dist2(p, w);
        var i = {x: v.x + q * (w.x - v.x), y: v.y + q * (w.y - v.y)};
        return dist2(p, i);
    
        function dist2(p, q) {
            return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
        }
    }
}

var route = [{x: 210, y: 190}, {x: 820, y: 480}, {x:530, y: 470}, {x: 440, y: 760}];
var distance = 100;
var mapSize = 1000;
var gridSize = 125;
var gridCells = Math.floor(mapSize / gridSize);
var grid = {close: [], far: []};

for (x = 0; x < gridCells; x++) {
    for (y = 0; y < gridCells; y++) {
        grid.far[y * (gridCells) + x] = {x: (x + 0.5) * gridSize, 
                                         y: (y + 0.5) * gridSize, 
                                       key: x + "," + y};
    }
}

isCloseToRoute(grid, route, distance + 0.707107 * gridSize);
alert(grid.close.length + " grid cells near route");
for (i in grid.close) console.log(grid.close[i].key);

我对 isCloseToRoute() 进行了更多优化。该示例运行一个测试,其中 1000 个随机点针对 1000 段随机路线进行检查。

function isCloseToRoute(points, route, distance) {
    var distance2 = Math.pow(distance, 2);
    for (var i = 0; i < route.length - 1; i++) {
        isCloseToLineSegment(route[i], route[i + 1]);
    }

    function isCloseToLineSegment(v, w) {
        var len2 = distanceToPoint2(v, w);
        var lenX = w.x - v.x, lenY = w.y - v.y;
        for (var i = points.far.length - 1; i >= 0; i--) {
            if (distanceToLineSegment2(points.far[i]) <= distance2) {
                points.near.push(points.far.splice(i, 1)[0]);
            }
        }

        function distanceToLineSegment2(p) {
          if (len2 == 0) return distanceToPoint2(p, v);   // enable if zero-length segments are possible
            var q = ((p.x - v.x) * lenX + (p.y - v.y) * lenY) / len2;
            if (q < 0) return distanceToPoint2(p, v);
            if (q > 1) return distanceToPoint2(p, w);
            var r = {x: v.x + q * lenX, y: v.y + q * lenY};
            return distanceToPoint2(p, r);
        }

        function distanceToPoint2(p, q) {
            return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
        }
    }
}

// generate random test data
var points = {near: [], far: [{x: 500, y: 500}]};
var route = [{x: 200, y: 200}];
var distance = 100;
for (var i = 1; i < 1000; i++) {
    points.far[i] = {x: Math.random() * 1000, y: Math.random() * 1000};
    route[i] = {x: route[i - 1].x + 3 * Math.random() - 1, y: route[i - 1].y + 3 * Math.random() - 1};
}

var t = new Date().getTime();
isCloseToRoute(points, route, distance);
t = new Date().getTime() - t;
alert(points.near.length + " points found near route.\n(1000 points checked against 1000 segments in " + t + " ms)");
for (i in points.near) console.log(points.near[i].x + "," + points.near[i].y);

【讨论】:

  • 这很有帮助。我会用这个。然而,在性能方面,如何从数据库中检索点的障碍,以便只有足够接近路径的点才能提交给这个操作。
  • 也许你可以把你的地图分成正方形,并在这些正方形中组织数据库,并且只加载靠近路线的正方形的点?
  • @JeanAlesi 我已经将它扩展为一个基本的路由检查功能,并进行了一些简单的优化。我不知道这对于要处理的数据量是否足够有效。
  • 是的.. 我认为这是个好主意。我正在尝试使用网格。不过,我想知道是否有专门的算法,因为 GPS 服务采用了类似的功能。
  • 存储点的最佳方式可能不是作为列表,而是作为空间分区树 (en.wikipedia.org/wiki/List_of_data_structures#Trees),例如四叉树,但我真的对此知之甚少,无法提出建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-10
  • 2011-09-28
  • 2010-10-25
  • 2021-11-05
  • 1970-01-01
  • 2018-01-17
相关资源
最近更新 更多