最近我遇到了一个问题,需要计算四个圆的并集面积(包括圆心和半径)。
例如下面的这张图片:
对于两个圆来说,计算起来很容易:
我可以计算每个圆中不在三角形内的部分所占的比例,然后计算三角形的面积。
但是当有两个以上的圆时,是否存在一种聪明的算法可以使用呢?
最近我遇到了一个问题,需要计算四个圆的并集面积(包括圆心和半径)。
例如下面的这张图片:
对于两个圆来说,计算起来很容易:
我可以计算每个圆中不在三角形内的部分所占的比例,然后计算三角形的面积。
但是当有两个以上的圆时,是否存在一种聪明的算法可以使用呢?
在外围找到所有圆的交点(例如下图中的B、D、F、H)。将它们与相应圆的中心连接起来形成一个多边形。圆的联合区域的面积等于多边形的面积加上由连续交点和它们之间的圆心定义的圆片的面积。您还需要考虑任何孔。
我相信有一个聪明的算法,但是这里有一个简单的算法可以避免寻找它:
当然这个算法比较愚蠢,但是:
Ants Aasma的答案给出了基本思路,但我想让它更具体化。请看下面的五个圆和它们的分解方式。
识别这三种类型的点很容易。现在构造一个图形数据结构,其中节点是蓝色点和带有白色内部的红色点。对于每个圆,将其边界上的每个交点(带有白色内部的红点)与圆中心(蓝点)之间建一条边。
这将把圆的并集分解为一组两两不相交的多边形(阴影蓝色区域)和圆形扇形(阴影绿色区域),并覆盖原始的并集(也就是说,这是一个分区)。由于每个区域都很容易计算面积,因此可以通过求和各个区域的面积来计算并集的面积。
对于与之前不同的解决方案,您可以使用四叉树生成任意精度的估算值。
如果您能确定一个正方形是内部、外部还是与形状相交,那么这也适用于任何形状联合。
每个单元格都有以下状态之一:空、满或部分。
该算法的步骤是在四叉树中"绘制"圆形,从低分辨率开始(例如4个单元格被标记为空)。每个单元格要么:
完成后,您可以计算面积的估算值:满单元格提供下限,空单元格提供上限,部分单元格提供最大面积误差。
如果误差对您来说太大,则可以细化部分单元格,直到获得正确的精度。
我认为这比几何方法更容易实现,因为几何方法可能需要处理很多特殊情况。
我喜欢对于2个相交圆的解决方法--这里是我如何使用稍微变化的同样方法来解决更为复杂的情况。
这可能会更好地帮助我们推广算法以适用于更多半重叠圆的情况。
区别在于,我开始链接中心(因此顶点位于圆心之间,而不是圆相交的地方)。我认为这样可以更好地进行推广。(在实践中,也许蒙特卡罗方法很值得一试)
(源自:secretGeek.net)
如果你想得到离散(而不是连续)的答案,你可以使用类似像素绘画算法的方法。
在网格上绘制圆形,然后对于大部分位于圆内的网格单元(即其至少50%的面积在一个圆内),着色每个网格单元。对整个网格执行此操作(其中网格覆盖所有由圆覆盖的区域),然后计算网格中着色单元的数量。
嗯,非常有趣的问题。 我的方法可能是以下步骤:
(此公式适用于任何形状,无论是圆还是其他形状)
area(A∪B) = area(A) + area(B) - area(A∩B)
在IT技术中,A ∪ B
表示A并B,A ∩ B
表示A交B(你可以从第一步得出结论)。
(这与上面相同,只是将A
替换为A∪B
)
area((A∪B)∪C) = area(A∪B) + area(C) - area((A∪B)∩C)
我们刚刚计算出了 area(A∪B)
,现在可以找到 area((A∪B)∩C)
:
area((A∪B)nC) = area((A∩C)∪(B∩C)) = area(A∩C) + area(A∩B) - area((A∩C)∩(B∩C)) = area(A∩C) + area(A∩B) - area(A∩B∩C)
在上面的内容中,您可以再次找到(A∩B∩C)区域。
棘手的部分是最后一步——添加的圆越多,它就变得越复杂。我相信有一种扩展方法可以计算有限并的交集面积,或者您可能能够递归地计算出它。
此外,关于使用蒙特卡罗方法来近似计算交集面积,我相信可以将任意数量的圆的交集缩小为其中4个圆的交集,这可以精确计算(但我不知道如何做到这一点)。
顺便说一下,可能有更好的方法——每增加一个圆,复杂度就显著增加(可能是指数级别的,但我不确定)。
<p>Area computation of arbitrary figures as done thru pixel-painting, in which a complex shape is drawn into an HTML5 canvas and the area determined by comparing the number of white pixels found in the resulting bitmap. See javascript source for details.</p>
<canvas id="canvas" width="80" height="100"></canvas>
<p>Area = <span id="result"></span></p>
// Get HTML canvas element (and context) to draw into
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Lil' circle drawing utility
function circle(x,y,r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2);
ctx.fill();
}
// Clear canvas (to black)
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Fill shape (in white)
ctx.fillStyle = 'white';
circle(40, 50, 40);
circle(40, 10, 10);
circle(25, 15, 12);
circle(35, 90, 10);
// Get bitmap data
var id = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = id.data; // Flat array of RGBA bytes
// Determine area by counting the white pixels
for (var i = 0, area = 0; i < pixels.length; i += 4) {
area += pixels[i]; // Red channel (same as green and blue channels)
}
// Normalize by the max white value of 255
area /= 255;
// Output result
document.getElementById('result').innerHTML = area.toFixed(2);