我猜我的问题与“凸包”有关,但不一样。图中的所有形状都是具有相同宽度和高度的矩形。许多人彼此相邻。我想将那些相邻的矩形组合成多边形。与“凸包”不同,被修复的多边形可能在内部是“空心的”。
是否有可用的开源算法?
答案 0 :(得分:12)
我必须编写一个算法来合并相邻的多边形,作为HTML5画布的实验项目的一部分(没有任何光彩,一个拼图:-)自然支持生成的多边形中的孔。 Javascript例程位于www dot raymondhill dot net / puzzle-rhill / jigsawpuzzle-rhill-3 dot js
中名为Polygon.prototype.merge()的函数中。关键是要删除重复但方向相反的段。粗略解释:A点是{x:?,y:?},Segment是{ptA:?,ptB:?},Contour是{pts:[]}(连接的Point对象的集合),Polygon是{contours:[]}(Contour对象的集合。)
合并算法在Segment对象的大型胖池中收集所有段,其中重复项被删除。首先,定义Polygon A的所有轮廓的所有部分都将添加到池中。然后,定义Polygon B的所有轮廓的所有段都被添加到池中,但我们测试并删除重复(使用Point对象作为哈希键轻松完成)。
然后我们从池中取一段(随机都很好),我们“走”它直到我们到达“死胡同”,也就是说,不再连接任何段。这形成一个Contour对象。我们重复,直到使用了整个段的集合。使用段时,它们将从池中删除。 “走”一段意味着我们得到它的终点,我们查找一个与起点匹配的段。
如上所述,结果我们有一组定义Polygon的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
答案 1 :(得分:3)
我会调查类似General Polygon Clipper的内容。你基本上做联合(OR)多边形操作。它们都是矩形的事实只会让数学变得容易一些,但它很容易用GPC之类的东西来完成。
那里也有许多语言的语言包装。
答案 2 :(得分:1)
如果您正在使用空间处理库(如JTS [java],NTS [.net]或GEOS [c ++],它们都是开源的,可用于商业应用程序,与GPC不同)您可以只使用矩形联合
这样做的一般目的是建立输入边缘的图形(矩形),执行交叉点,在结果的内部或外部标记边缘,并保持外边缘。我不知道处理矩形的特定算法,但它可能是类似的,除非,如上所述,数学会更简单。
答案 3 :(得分:0)
如果您的边界合理,则使用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 )
抱歉,这个伪代码很邋,,但你应该得到一般的想法
答案 4 :(得分:0)
尝试以下操作怎么样?我认为如果设计得当,这将有效。
找到最小的emclosing矩形,基本上是max-x,min-x和max-y以及min-y。这将是我们的绘画画布。初始化一个二维位数dx X dy,其中dx,dy是这个外部矩形的宽度,全部为0。
我们的目标是找到轮廓,基本上是矩形的一些角落,这样我们就可以将这个问题缩小到我们可以计算处理它的水平,一旦我们得到了积分,我们可以扩展到实际坐标。
扫描上面的2D数组并标记点1,如果它包含在给定矩形之一中。
现在扫描2D阵列并查找其邻域具有3:1分割的点,这意味着在3个边上它具有1并且在一侧具有0s,反之亦然。这些点是定义轮廓的点。
如果我们能够明智地缩小问题,我认为复杂性是可以管理的。
答案 5 :(得分:0)
我找到了一种更简单的方法:
就是这样!
答案 6 :(得分:0)
我之前遇到过类似的问题。我不确切地知道你的点是如何对齐的,但我的距离总是相隔5米。
我的解决方案是按x坐标排序。
有两个列表,一个名为previous,另一个名为current。
如果当前为空,则将该点添加到当前。如果当前不为空,则检查该点是否与当前其中一个点相邻(向后运行列表,因为最近一个点相邻的可能性更高)
如果该点不与当前任何点相邻,则检查当前任何点是否与前面列表中的任何点相邻。 如果是,则合并(concat)它们,如果没有则将之前的点移动到另一个包含完整多边形的列表,然后设置previous = current,empty current并将正在处理的点添加到当前。
根据您的点数(处理顺序),您可能需要再次运行所有最终多边形,以检查它们是否与任何其他多边形相邻。
对于长篇文章感到抱歉,如果您想要编码,请告诉我,它在c#中并且不是很干净。
答案 7 :(得分:-1)
只需测试矩形是否接触,然后在它们的点的并集上运行凸包。
或者您也可以手动测试以查看矩形的哪一侧正在触摸,并以正确的顺序将点添加到多边形对象。
这些假设闭合多边形就足够了(不能有洞)。