两个圆的交集面积

16

给定两个圆:

  • 圆C1的圆心坐标为(x1, y1),半径为radius1
  • 圆C2的圆心坐标为(x2, y2),半径为radius2

如何计算它们的交集面积?当然可以使用所有标准的数学函数(如sincos等)。


http://mathworld.wolfram.com/Circle-CircleIntersection.html - Johan Kotlinski
谢谢。在发帖之前,我实际上已经知道这个链接。我实际上是在寻找使用我列出的变量的特定方程。 - Chris Redford
5个回答

32

好的,使用 Wolfram 链接和 Misnomer 的提示查看方程式14,我已经使用我列出的变量和中心之间的距离(可以从它们中轻松地推导出来)推导出了以下 Java 解决方案:

double r = radius1;
double R = radius2;
double d = distance;
if(R < r){
    // swap
    r = radius2;
    R = radius1;
}
double part1 = r*r*Math.acos((d*d + r*r - R*R)/(2*d*r));
double part2 = R*R*Math.acos((d*d + R*R - r*r)/(2*d*R));
double part3 = 0.5*Math.sqrt((-d+r+R)*(d+r-R)*(d-r+R)*(d+r+R));

double intersectionArea = part1 + part2 - part3;

1
是的,我已经检查过了,已经有一个帖子,官方投票答案是回来回答自己的问题是好的。但似乎大多数SO用户并没有遵循这个政策。没有冒犯Misnomer,我很感激他的帮助。但是,现在,他因为给我一个我在发布之前就已经知道的链接而获得了3个赞,而我已经将该链接中的信息翻译成代码,并且只有1个赞。我在SO上反复看到这种群体行为,这让我感到困惑。 - Chris Redford
1
关于给自己回答奖励积分的问题 - 这会让系统过于容易被操纵。从哲学上讲,只有当你为他人提供价值时才应该获得奖励,而不是为自己。关于对回答进行负评 - 在我看来,这只有在回答错误或误导时才应该这样做。即使回答并不完全符合你的要求,但如果它提供了有用的信息,仍然可能对其他人有用。 - broofa
3
此外,值得一提的是,在错误条件及其含义方面,可能需要添加解释性注释(和代码)。例如,如果平方根中的表达式小于0或为负数,或者acos中的表达式介于-1和1之间,则需要进行说明。 - broofa
1
@user1145925 当r=1,R=2,d=1时,正确的交叉区域实际上是Pi,而不是Pi/2。 - rolve
@rolve 哇,我觉得我快疯了.. 嗯,没事,这个答案挺好的。 - user1145925
显示剩余3条评论

25

这是一个 JavaScript 函数,完全可以满足 Chris 的需求:

function areaOfIntersection(x0, y0, r0, x1, y1, r1)
{
    var rr0 = r0 * r0;
    var rr1 = r1 * r1;
    var d = Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
    var phi = (Math.acos((rr0 + (d * d) - rr1) / (2 * r0 * d))) * 2;
    var theta = (Math.acos((rr1 + (d * d) - rr0) / (2 * r1 * d))) * 2;
    var area1 = 0.5 * theta * rr1 - 0.5 * rr1 * Math.sin(theta);
    var area2 = 0.5 * phi * rr0 - 0.5 * rr0 * Math.sin(phi);
    return area1 + area2;
}

然而,如果一个圆完全在另一个圆内部或两个圆根本没有接触,则此方法将返回NaN。一个稍微不同的版本,在这些情况下不会失败,如下所示:

function areaOfIntersection(x0, y0, r0, x1, y1, r1)
{
    var rr0 = r0 * r0;
    var rr1 = r1 * r1;
    var d = Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));

    // Circles do not overlap
    if (d > r1 + r0)
    {
    return 0;
    }

    // Circle1 is completely inside circle0
    else if (d <= Math.abs(r0 - r1) && r0 >= r1)
    {
    // Return area of circle1
    return Math.PI * rr1;
    }

    // Circle0 is completely inside circle1
    else if (d <= Math.abs(r0 - r1) && r0 < r1)
    {
    // Return area of circle0
    return Math.PI * rr0;
    }

    // Circles partially overlap
    else
    {
    var phi = (Math.acos((rr0 + (d * d) - rr1) / (2 * r0 * d))) * 2;
    var theta = (Math.acos((rr1 + (d * d) - rr0) / (2 * r1 * d))) * 2;
    var area1 = 0.5 * theta * rr1 - 0.5 * rr1 * Math.sin(theta);
    var area2 = 0.5 * phi * rr0 - 0.5 * rr0 * Math.sin(phi);
    
    // Return area of intersection
    return area1 + area2;
    }
}

我通过阅读Math Forum上找到的信息编写了这个函数。相比于Wolfram MathWorld的解释,我发现这个更清晰。


谢谢 - 它确实帮了我大忙!修改后的版本正是我想要的。 - scipilot
这个答案是错误的。我不完全确定逻辑错误在哪里,但至少对于以下情况失败了(当r1从下方接近2时):x0=0,y0=0,r0=1,x1=1,y0=0,r1=2。具体来说,您的代码给出趋近于Pi的面积,而实际上应该趋近于Pi/2。 - user1145925
1
@user1145925,对于输入x0=0,y0=0,r0=1,x1=1,y0=0,r1=2,输出Pi是正确的,因为圆0的面积为Pi,并且完全包含在圆2内。 - Caleb Vear

15

您可能希望查看这个解析解决方案,并使用您的输入值应用公式。

当半径相等时,另一个公式在此处给出:

Area = r^2*(q - sin(q))  where q = 2*acos(c/2r),
where c = distance between centers and r is the common radius.

1
+1 第一个链接有公式的完整推导。如果圆的半径相同,则第二个链接很有用。 - eaj
谢谢提供的链接。是否有一个更通用的公式可以根据我给出的变量推导出来(例如,不假设通用半径)? - Chris Redford
1
我认为第一个链接eq 14给出了一个通用解决方案,但我需要复习数学才能看清楚到底发生了什么...此外,你可能需要更多的东西来找到这个解决方案,也可以尝试这个链接,如果有帮助的话-http://2000clicks.com/MathHelp/GeometryConicSectionCircleIntersection.aspx - Vishal
好的。我希望有一个直接使用我列出的变量的方程式。我会尝试研究方程式14,并找到一种将我所拥有的变量代入其中的方法。 - Chris Redford

2

以下是Python的一个示例。

"""Intersection area of two circles"""

import math
from dataclasses import dataclass
from typing import Tuple


@dataclass
class Circle:
    x: float
    y: float
    r: float

    @property
    def coord(self):
        return self.x, self.y


def find_intersection(c1: Circle, c2: Circle) -> float:
    """Finds intersection area of two circles.

    Returns intersection area of two circles otherwise 0
    """

    d = math.dist(c1.coord, c2.coord)
    rad1sqr = c1.r ** 2
    rad2sqr = c2.r ** 2

    if d == 0:
        # the circle centers are the same
        return math.pi * min(c1.r, c2.r) ** 2

    angle1 = (rad1sqr + d ** 2 - rad2sqr) / (2 * c1.r * d)
    angle2 = (rad2sqr + d ** 2 - rad1sqr) / (2 * c2.r * d)

    # check if the circles are overlapping
    if (-1 <= angle1 < 1) or (-1 <= angle2 < 1):
        theta1 = math.acos(angle1) * 2
        theta2 = math.acos(angle2) * 2

        area1 = (0.5 * theta2 * rad2sqr) - (0.5 * rad2sqr * math.sin(theta2))
        area2 = (0.5 * theta1 * rad1sqr) - (0.5 * rad1sqr * math.sin(theta1))

        return area1 + area2
    elif angle1 < -1 or angle2 < -1:
        # Smaller circle is completely inside the largest circle.
        # Intersection area will be area of smaller circle
        # return area(c1_r), area(c2_r)
        return math.pi * min(c1.r, c2.r) ** 2
    return 0


if __name__ == "__main__":

    @dataclass
    class Test:
        data: Tuple[Circle, Circle]
        expected: float

    tests = [
        Test((Circle(2, 4, 2), Circle(3, 9, 3)), 0),
        Test((Circle(0, 0, 2), Circle(-1, 1, 2)), 7.0297),
        Test((Circle(1, 3, 2), Circle(1, 3, 2.19)), 12.5664),
        Test((Circle(0, 0, 2), Circle(-1, 0, 2)), 8.6084),
        Test((Circle(4, 3, 2), Circle(2.5, 3.5, 1.4)), 3.7536),
        Test((Circle(3, 3, 3), Circle(2, 2, 1)), 3.1416)
    ]

    for test in tests:
        result = find_intersection(*test.data)
        assert math.isclose(result, test.expected, rel_tol=1e-4), f"{test=}, {result=}"

    print("PASSED!!!")


2

我正在制作一个角色生成工具,基于圆形相交……您可能会发现它有用。

可以动态提供圆:

    C: {
        C1: {id: 'C1',x:105,y:357,r:100,color:'red'},
        C2: {id: 'C2',x:137,y:281,r:50, color:'lime'},
        C3: {id: 'C3',x:212,y:270,r:75, color:'#00BCD4'}
    },

检查完整的 fiddle... FIDDLE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接