I am adding a second answer instead of expanding the first one, because I am quite positive that the other answer is correct, but I am not so sure if this answer is as correct. I have done some simple testing, and it seems to work, but please point out my errors.
This is basically a quick-n-dirty implementation of what I stated before:
from math import atan2, pi
def intersectLine (a, b):
s0, e0, s1, e1 = a [0], a [1], b [0], b [1]
s = max (s0, s1)
e = min (e0, e1)
if e <= s: return (0, 0)
return (s, e)
class SectorSet:
def __init__ (self, sectors):
self.sectors = sectors [:]
def __repr__ (self):
return repr (self.sectors)
def __iadd__ (self, other):
acc = []
for mine in self.sectors:
for others in other.sectors:
acc.append (intersectLine (mine, others) )
self.sectors = [x for x in acc if x [0] != x [1] ]
return self
class Circle:
CONTAINS = 0
CONTAINED = 1
DISJOINT = 2
INTERSECT = 3
def __init__ (self, x, y, r):
self.x = float (x)
self.y = float (y)
self.r = float (r)
def intersect (self, other):
a, b, c, d, r0, r1 = self.x, self.y, other.x, other.y, self.r, other.r
r0sq, r1sq = r0 ** 2, r1 ** 2
Dsq = (c - a) ** 2 + (d - b) ** 2
D = Dsq ** .5
if D >= r0 + r1:
return Circle.DISJOINT, None, None
if D <= abs (r0 - r1):
return Circle.CONTAINED if r0 < r1 else Circle.CONTAINS, None, None
dd = .25 * ( (D + r0 + r1) * (D + r0 - r1) * (D - r0 + r1) * (-D + r0 + r1) ) ** .5
x = (a + c) / 2. + (c - a) * (r0sq - r1sq) / 2. / Dsq
x1 = x + 2 * (b - d) / Dsq * dd
x2 = x - 2 * (b - d) / Dsq * dd
y = (b + d) / 2. + (d - b) * (r0sq - r1sq) / 2. / Dsq
y1 = y - 2 * (a - c) / Dsq * dd
y2 = y + 2 * (a - c) / Dsq * dd
return Circle.INTERSECT, (x1, y1), (x2, y2)
def angle (self, point):
x0, y0, x, y = self.x, self.y, point [0], point [1]
a = atan2 (y - y0, x - x0) + 1.5 * pi
if a >= 2 * pi: a -= 2 * pi
return a / pi * 180
def sector (self, other):
typ, i1, i2 = self.intersect (other)
if typ == Circle.DISJOINT: return SectorSet ( [ (0, 360) ] )
if typ == Circle.CONTAINS: return SectorSet ( [ (0, 360) ] )
if typ == Circle.CONTAINED: return SectorSet ( [] )
a1 = self.angle (i1)
a2 = self.angle (i2)
if a1 > a2: return SectorSet ( [ (0, a2), (a1, 360) ] )
return SectorSet ( [ (a1, a2) ] )
def outline (self, others):
sectors = SectorSet ( [ (0, 360) ] )
for other in others:
sectors += self.sector (other)
u = 2 * self.r * pi
total = 0
for start, end in sectors.sectors:
total += (end - start) / 360. * u
return total
def outline (circles):
total = 0
for circle in circles:
others = [other for other in circles if circle != other]
total += circle.outline (others)
return total
a = Circle (0, 0, 2)
b = Circle (-2, -1, 1)
c = Circle (2, -1, 1)
d = Circle (0, 2, 1)
print (outline ( [a, b, c, d] ) )
Formula for calculating the intersecting points of two circles shamelessly stolen from here.