def _is_in_circle(circle, p):
*center, radius = circle
dist = math.dist(center, p)
return dist <= radius * MULTIPLICATIVE_EPS
def _circle_from_pq(points, p, q):
circle = ((p[0] + q[0]) * 0.5, (p[1] + q[1]) * 0.5, math.dist(p, q) * 0.5)
cw_circle, ccw_circle = None, None
ccw_area, cw_area = -INF, INF
for r in points:
if _is_in_circle(circle, r):
continue
ori = orientation(p, q, r)
if ori == Orientation.COLLINEAR:
continue
*center, radi = circle = circumcircle_of_triangle(p, q, r)
area = signed_parallelogram_area(p, q, center)
if ori == Orientation.CCW and area > ccw_area:
ccw_area, ccw_circle = area, circle
elif ori == Orientation.CW and area < cw_area:
cw_area, cw_circle = area, circle
if cw_circle is None and ccw_circle is None:
return circle
elif cw_circle is None:
return ccw_circle
elif ccw_circle is None:
return cw_circle
else:
return cw_circle if cw_circle[2] < ccw_circle[2] else ccw_circle
def smallest_enclosing_circle(orig_points: Iterable[PointF]) -> CircleF:
points = list(orig_points)
if len(points) == 1:
return (*points[0], 0)
if len(points) == 2:
p, q = points
return (p[0] + q[0]) * 0.5, (p[1] + q[1]) * 0.5, math.dist(p, q) * 0.5
random.shuffle(points)
circle = (0, 0, 0)
for i, p in enumerate(points):
if not _is_in_circle(circle, p):
circle = (0, 0, 0)
for j, q in enumerate(points[:i]):
if not _is_in_circle(circle, q):
circle = _circle_from_pq(points[:j], p, q)
return circle