将相邻矩形合并为多​​边形的算法

Posted

技术标签:

【中文标题】将相邻矩形合并为多​​边形的算法【英文标题】:Algorithm to merge adjacent rectangles into polygon 【发布时间】:2010-10-13 05:46:22 【问题描述】:

我猜我的问题与“凸壳”有关,但不一样。图中的所有形状都是具有相同宽度和高度的矩形。许多彼此相邻。我想将那些相邻的矩形组合成多边形。与“凸壳”不同,生成的多边形内部可能是“空心的”。

有没有可用的开源算法?

【问题讨论】:

任何相邻矩形块的周长构成一个多边形。您的问题是“如何列出定义一组连接矩形的周长的线段?”还是别的什么? 当您说“许多彼此相邻”时,这是什么意思?它们只是在边缘接触,还是矩形可以重叠?矩形是某种网格上的,还是它们的顶点可以在任何地方? 【参考方案1】:

我会研究类似General Polygon Clipper 的东西。你基本上是在做联合(OR)多边形操作。它们都是矩形的事实只是使数学变得更容易一些,但可以使用 GPC 之类的东西轻松完成。

那里也有许多语言的语言包装器。

【讨论】:

【参考方案2】:

只需测试矩形是否接触,然后在它们的点的联合上运行凸包。

或者您也可以手动测试以查看矩形的哪一侧接触,然后以正确的顺序将点添加到多边形对象。

这些假设封闭的多边形就足够了(其中不能有洞)。

【讨论】:

如果他想保留漏洞,那是行不通的。想象一下,有一个 3x3 的矩形块,但中心矩形缺失 - 凸包将填充它。【参考方案3】:

如果您使用的是空间处理库(如 JTS [java]、NTS [.net] 或 GEOS [c++],它们都是开源且可用于商业应用程序,与 GPC 不同),您可以将矩形合并.

执行此操作的通用方法是构建输入(矩形)的边缘图,执行交集,将边缘标记为结果的内部或外部,并仅保留外部边缘。我不知道处理矩形的特定算法,但它可能是类似的,除非如上所述,数学会更简单。

【讨论】:

【参考方案4】:

如果您的边界合理,请使用 2D 边数数组,否则您必须使用嵌套字典。

因为所有的宽度和高度都相同,所以您可以通过 x、y 和方向(垂直或水平)的组合来唯一标识一条边

示例伪代码: list_of_edges = 新列表 arr_count = new int[][][]

fill_with_zeroes(arr_count )

foreach rect
   foreach edge
      arr_count [edge.x][edge.y][edge.orientation] += 1

foreach edge in arr_count
   if count[edge] = 1
      list_of_edges.add(edge]

当然,如果要对边进行排序,则必须再次通过数组

foreach edge in arr_count
    if count[edge] = 1
        add_recursive(edge)

add_recursive(x,y):
    for both horizontal and vertical orientations of edge starting at x, y:
    count[edge] = 0
    if (edge.orientation is horizontal)
        return add_recursive( x+1, y)
    else 
        return add_recursive( x, y+1 )

对不起,这个伪代码有点草率,但你应该明白大体的想法

【讨论】:

【参考方案5】:

我不得不编写一个用于合并相邻多边形的算法,作为 html5 画布的实验项目的一部分(没什么光彩的,拼图游戏 :-) 生成的多边形中的孔自然是受支持的。 javascript 例程位于 www dot raymondhill dot net /uzzle-rhill / jigsawpuzzle-rhill-3 dot js 中名为 Polygon.prototype.merge() 的函数中

关键是删除重复但方向相反的段。粗略解释:一个Point是x:?,y:?,一个Segment是ptA:?,ptB:?,一个Contour是pts:[](连接的Point对象的集合),一个Polygon是contours:[](Contour 对象的集合。)

合并算法将所有个段收集到一个大的 Segment 对象池中,其中重复项被消除。首先,将定义多边形 A 的所有轮廓的所有段添加到池中。然后,定义多边形 B 的所有轮廓的所有线段都添加到池中,但我们测试并删除重复项(使用 Point 对象作为哈希键很容易完成)。

然后我们从池中取出一个段(随机就好),我们“走”它,直到我们到达一个“死胡同”,即不能再连接更多段。这形成一个轮廓对象。我们重复直到使用了整个段集合。使用段时,会将它们从池中删除。 “走”一个段意味着我们取它的终点,然后我们查找一个起点与它匹配的段。

如前所述,因此我们有一个定义多边形的 Contour 对象的集合。有些轮廓将被填充,有些可能是空心的。要确定一个轮廓是填充还是空心,只是测试轮廓是顺时针还是逆时针,或者它的面积是正还是负。这是一个约定,在我的情况下,顺时针轮廓是填充的,逆时针是空心的。

这是我的实现,减去细节和错误处理。希望我复制/粘贴的内容足以让您立即使用,否则请参阅上面的 JS 文件了解上下文:

// Point object
function Point(a,b) 
    // a=x,b=y
    if (b) 
        this.x=a;
        this.y=b;
        
    // a=Point or x:?,y:?
    else if (a) 
        this.x=a.x;
        this.y=a.y;
        
    // empty
    else 
        this.x=this.y=0;
        
    
Point.prototype.toHashkey = function() 
    return this.x+"_"+this.y;
    ;
Point.prototype.clone = function() 
    return new Point(this);
    ;
// Segment object
function Segment(a,b) 
    this.ptA = new Point(a);
    this.ptB = new Point(b);
    
// Contour object
function Contour(a) 
    this.pts = []; // no points
    if (a) 
        if (a instanceof Array)  // assume array of Point objects
            var nPts = a.length;
            for (var iPt=0; iPt<nPts; iPt++) 
                this.pts.push(a[iPt].clone());
                
            
        
    
Contour.prototype.clone = function() 
    return new Contour(this);
    ;
Contour.prototype.addPoint = function(p) 
    this.pts.push(p);
    ;
// Polygon object
function Polygon(a) 
    this.contours = []; // no contour
    if (a) 
        if (a instanceof Polygon) 
            var contours = a.contours;
            var nContours = contours.length;
            for ( var iContour=0; iContour<nContours; iContour++ ) 
                this.contours.push(new Contour(contours[iContour]));
                
             
        else if ( a instanceof Array ) 
            this.contours.push(new Contour(a));
            
        
    
Polygon.prototype.merge = function(other) 
    // A Javascript object can be used as an associative array, but
    // they are not real associative array, as there is no way
    // to query the number of entries in the object. For this
    // reason, we maintain an element counter ourself.
    var segments=;
    var contours=this.contours;
    var nContours=contours.length;
    var pts; var nPts;
    var iPtA; var iPtB;
    var idA; var idB;
    for (var iContour=0; iContour<nContours; iContour++) 
        pts = contours[iContour].pts;
        nPts = pts.length;
        iPtA = nPts-1;
        for ( iPtB=0; iPtB<nPts; iPtA=iPtB++ ) 
            idA = pts[iPtA].toHashkey();
            idB = pts[iPtB].toHashkey();
            if (!segments[idA]) 
                segments[idA]=n:0,pts:;
                
            segments[idA].pts[idB] = new Segment(pts[iPtA],pts[iPtB]);
            segments[idA].n++;
            
        
    // enumerate segments in other's contours, eliminate duplicate
    contours = other.contours;
    nContours = contours.length;
    for ( iContour=0; iContour<nContours; iContour++ ) 
        pts = contours[iContour].pts;
        nPts = pts.length;
        iPtA=nPts-1;
        for (iPtB=0; iPtB<nPts; iPtA=iPtB++) 
            idA = pts[iPtA].toHashkey();
            idB = pts[iPtB].toHashkey();
            // duplicate (we eliminate same segment in reverse direction)
            if (segments[idB] && segments[idB].pts[idA]) 
                delete segments[idB].pts[idA];
                if (!--segments[idB].n) 
                    delete segments[idB];
                    
                
            // not a duplicate
            else 
                if (!segments[idA]) 
                    segments[idA]=n:0,pts:;
                    
                segments[idA].pts[idB] = new Segment(pts[iPtA],pts[iPtB]);
                segments[idA].n++;
                
            
        
    // recreate and store new contours by jumping from one point to the next,
    // using the second point of the segment as hash key for next segment
    this.contours=[]; // regenerate new contours
    var contour;
    for (idA in segments)  // we need this to get a starting point for a new contour
        contour = new Contour();
        this.contours.push(contour);
        for (idB in segments[idA].pts) break;
        segment = segments[idA].pts[idB];
        while (segment) 
            contour.addPoint(new Point(segment.ptA));
            // remove from collection since it has now been used
            delete segments[idA].pts[idB];
            if (!--segments[idA].n) 
                delete segments[idA];
                
            idA = segment.ptB.toHashkey();
            if (segments[idA]) 
                for (idB in segments[idA].pts) break; // any end point will do
                segment = segments[idA].pts[idB];
                
            else 
                segment = null;
                
            
        
    ;

当我们“走”段来创建轮廓时,有一种情况是一个段可以连接到多个段:

+------+-------+
|   Poly A     | two segments sharing same start point Z
|              | 
+      +---<---Z---->---+
|      |       | Poly B |
|      |       |        |
+      +-------+--------+
|                       |
|                       |
+------+-------+--------+

这会导致两个有效的结果(上面的算法会随机导致一个或另一个):

结果 1,一个填充轮廓:

+------+--->---+
|   Poly A     |
|              | 
+      +---<---+---->---+
|      |       |        |
|      |       |        |
+      +--->---+        +
|                       |
|                       |
+------+---<---+--------+

结果2,一个填充轮廓,一个空心轮廓:

+------+--->---+
|   Poly A     |
|              | 
+      +---<---+---->---+
|      | Hole A|        |
|      |       |        |
+      +--->---+        +
|                       |
|                       |
+------+---<---+--------+

但这应该不是问题,因为您的代码应该已经准备好处理漏洞。

其他重要细节:上面的算法没有去掉中间点('+'),实际上它们是预期的,否则算法将无法工作,如下例所示:

+------>-------+
|   Poly A     |
|              | 
|              | +---->---+
|              | | Poly B |
|              | |        |
|              | +----<---+
|              |
|              |
+------<-------+

我的理解是,这就是你所拥有的。我想可以通过预先查找和添加相交点来扩展该算法以支持这种情况(在我的情况下这是不必要的):

+------>-------+
|   Poly A     |
|              | 
|              + +---->---+
|              | | Poly B |
|              | |        |
|              + +----<---+
|              |
|              |
+------<-------+

希望对您有所帮助。

P.S.:您可以使用拼图游戏“测试”算法,将碎片拼在一起生成孔等:http://www.raymondhill.net/puzzle-rhill/puzzle-rhill.php?puzzlePieces=16&puzzleComplexity=1&puzzleURL=http://www.public-domain-photos.com/free-stock-photos-4/travel/los-angeles/los-angeles-skyline.jpg&puzzleRotate=0&puzzleVersion=3

【讨论】:

感谢您的回答,我能够在带有 OpenLayers 库的制图上下文中使用该算法。【参考方案6】:

试试下面的怎么样。我认为如果设计得当,这将起作用。

    找到最小的包围矩形,基本上是 max-x、min-x 和 max-y 和 min-y。这将是我们的画布。初始化一个 dx X dy 位的二维数组,其中 dx, dy 是这个外部矩形的宽度,全部为 0。

    我们的目标是找到轮廓,基本上是矩形的一些角,这样我们就可以将这个问题缩小到可以计算处理的水平,一旦我们有了点,我们就可以放大得到实际的坐标。

    扫描上述二维数组并标记点 1,如果它包含在给定的矩形之一中。

    现在扫描 2D 数组并查找其邻域具有 3:1 分割的点,这意味着它在 3 侧有 1,在一侧有 0,反之亦然。这些点将定义轮廓。

我认为如果我们能够明智地缩小问题的规模,复杂性将是可控的。

【讨论】:

【参考方案7】:

我找到了一个更简单的方法:

    创建黑色图像。 在图像上以白色绘制填充矩形。这样所有重叠的矩形都将连接起来。 找到斑点的轮廓。

就是这样!

【讨论】:

【参考方案8】:

我之前也遇到过类似的问题。我不知道你的点是如何对齐的,但我的点总是彼此间隔 5 米。

我的解决方案是按 x 坐标排序。

有两个列表,一个称为先前列表,一个称为当前列表。

如果当前为空,则将该点添加到当前。如果 current 不为空,则检查该点是否与 current 中的某个点相邻(向后运行列表,因为最近的点相邻的可能性更高)

如果该点与当前列表中的任何点都不相邻,则检查当前列表中的任何点是否与之前列表中的任何点相邻。 如果是,则合并(concat)它们,如果不是,则将点从上一个列表移动到另一个包含完整多边形的列表,然后设置上一个=当前,清空当前并将正在处理的点添加到当前。

根据您的点的处理方式(顺序),您可能需要再次运行所有最终多边形以检查它们是否与任何其他多边形相邻。

对不起,长长的文字墙,如果你想编码,请告诉我,它是用 c# 编写的,不是很干净。

【讨论】:

以上是关于将相邻矩形合并为多​​边形的算法的主要内容,如果未能解决你的问题,请参考以下文章

四边形不等式优化DP——石子合并问题 学习笔记

合并石子 四边形不等式优化

四边形不等式COGS1658- [HZOI 2014] 合并石子

自动合并相邻的矩形

合并许多凸多边形的快速算法或库

51nod - 1022四边形不等式优化DP