Defining a Circle
Class#
When it works well, the idea of object-oriented programming (OOP) is for the code you write to be composable — that is, like Legos, you should be able to click together different things you’ve built to make something that is more than the sum of its parts.
To illustrate this concept, in this class you will define a Circle
class in a manner that builds on the Point class you wrote in your previous assignment.
If creating Point or Circle objects feels esoteric or silly, don’t be fooled — being able to do geometric operations is key to a wide range of applications. Perhaps the most well known is geospatial analysis (often referred to as GIS, which is short for Geospatial Information Systems) — the science of understanding how things relate to one another spatially in the real world.
Point
and Circle
objects are two of the primitives GIS analysts use on a daily basis. Points can represent anything from an election polling place to the location of a source of airborne pollution or a ship at sea. Circles (and tools for measuring overlaps between circles or circles and points) are used to identify people or animals within a given radius of a point, or an area that may be exposed to a certain intensity of radiation generated from a point. Indeed, in these exercises we’re basically recreating some of the primitives used in one of the most important software libraries in the Python GIS ecosystem (shapely).
Bringing In Your Point Class#
The first thing you will put at the top of your code for this assignment — which should be in a file called circle.py
— will be the line:
from point import Point
Provided your point.py
file is in the same directory as your circle.py
file, this line will import your Point class and make it accessible from within this circle.py
file. You can refer to it as just Point
(like p = Point()
). Note that if we had just said import point
instead of from point import Point
, you would need to write p = point.Point()
to create a point object.
Note we have now come… full circle (sorry, couldn’t resist) with that
if __name__ == "__main__":
if statement. As mentioned before, code that is in thatif
block is only ever executed when a file is executed directly. But at the time, what it meant to not execute a file directly may have been unclear. Now you know! When we runfrom point import Point
, Python is running all the code inpoint.py
that defines the classPoint
and its functionality so it becomes accessible inside of thiscircle.py
program. But it won’t run the tests at the bottom ofpoint.py
because thatif
statement does not evaluate toTrue
.
When you submit your assignment to gradescope, you will need to zip circle.py
and point.py
into a single .zip
file and upload them together since the autograder needs both files run!
Circle Requirements#
For the Circle class, you will need:
A Docstring summarizing the class (a string you put right under the class definition).
An
__init__
method: takes aPoint
c and a numberr
and assigns the center and radius respectively.The default values should be a
Point
at (0, 0) and radius1
.You should assert that the radius is greater than or equal to
0
.Because
Point
is a mutable type, so you do not want to actually make the default valuePoint(0, 0)
as that will cause problems (if you don’t remember why, you might want to revisit the reading “Default Arguments Revisited” in Coursera PPF, Module 4 “Diving Deeper with Lists”, in subsection “Looking More into Lists.” Here’s a link that might work but hard to link into Coursera courses.). Instead, you use the “default value is None, check for None, make the value you want” idiom that you learned in that reading:
def func(x=None): if x is None: x = Point(0,0)
A
__str__
method: returns a string representation of the circle. For example, a circle with center(0, 0)
with radius1
would be:"((0, 0), 1)"
.A
move
method: takesdx
anddy
and moves the center of the circle accordingly. Remember, you already wrote move for Point!An
intersection_area
method: takes a parameterother_circle
and calculates the intersection ofself
, andother_circle
.
Calculating Intersection Area#
Remember that a question like “what is the formula for the intersection area of two circles?” indicates a lack of domain knowledge (not a lack of programming skill). In such situations, you should consult an authoritative source in that domain. For this problem, the following source derives the equation you need: https://mathworld.wolfram.com/Circle-CircleIntersection.html.
Specifically, you need equation (14) on that page. You can compute \(cos^{-1}(x)\) with math.acos(x)
in Python.
Note that the formula described above is for the case where the circles partially overlap. You will need to determine if the circles do not overlap at all, partially overlap, or overlap completely. If they do not overlap at all, the intersection area is 0. If they partially overlap, use the formula above. If they overlap completely, the intersection area is the area of the smaller circle.
We recommend drawing some pictures to figure help you figure out the algorithm to decide which case two circles fall into.
If you are feeling really stuck on the math, some code for the intersection area of partially overlapping circles is folded below, but please only use it after you’ve tried to figure things out yourself!
p1 = (
small_r
* small_r
* math.acos((d * d + small_r * small_r - big_r * big_r) / (2 * d * small_r))
)
p2 = (
big_r
* big_r
* math.acos((d * d + big_r * big_r - small_r * small_r) / (2 * d * big_r))
)
p3 = 0.5 * math.sqrt(
(-d + small_r + big_r)
* (d + small_r - big_r)
* (d - small_r + big_r)
* (d + small_r + big_r)
)
intersection_area = p1 + p2 - p3
where small_r
is the smaller radius and big_r
is the larger radius and d
is the distance between radii. Note this is ONLY the answer for partially overlapping circles.
Scaffolding#
Please copy-paste the following code into your circle.py
file as a starting place.
import math
from point import Point
class Circle:
# write a docstring here!
def __init__(self, c=None, r=1):
# WRITE ME
pass
def __str__(self):
# WRITE ME
return ""
def __repr__(self):
return "Circle" + str(self)
def move(self, dx, dy):
# WRITE ME
# hint: you should be calling move(dx,dy)
# on the Point that is the center of the circle
pass
def intersection_area(self, other_circle):
# WRITE ME
# first check if no overlap
# if no overlap, return 0
# next check for total overlap
# if so, return area of smaller circle
# otherwise, use Wolfram formula for partial overlap
return 0
pass
if __name__ == "__main__":
c1 = Circle()
# note that c2 just touches, but does not intersect c1
c2 = Circle(Point(3, 4), 5)
c3 = Circle(Point(), 2)
c4 = Circle(Point(4, 5), 6)
c5 = Circle(Point(-2, -2), 3)
c6 = Circle(Point(3, -3), 2)
circles = [c1, c2, c3, c4, c5, c6]
for ca in circles:
print("---")
print(str(ca))
print(repr(ca))
for cb in circles:
print("{:.8f}".format(ca.intersection_area(cb)))
pass
pass
c1.move(-1, 0)
c2.move(1, 1)
c3.move(2, -4)
for ca in circles:
print("---")
print(str(ca))
print(repr(ca))
for cb in circles:
print("{:.8f}".format(ca.intersection_area(cb)))
pass
pass
pass
Testing#
As within point.py
, this file includes some basic tests at the end of circle.py. The expected output is folded below. As usual, you are encouraged to do more testing to become confident in your code.
---
((0, 0), 1)
Circle((0, 0), 1)
3.14159265
1.50406278
3.14159265
0.74638183
1.80090139
0.00000000
---
((3, 4), 5)
Circle((3, 4), 5)
1.50406278
78.53981634
5.74769069
76.19439808
0.21244868
0.00000000
---
((0, 0), 2)
Circle((0, 0), 2)
3.14159265
5.74769069
12.56637061
4.28297693
6.03868357
0.00000000
---
((4, 5), 6)
Circle((4, 5), 6)
0.74638183
76.19439808
4.28297693
113.09733553
0.00000000
0.00000000
---
((-2, -2), 3)
Circle((-2, -2), 3)
1.80090139
0.21244868
6.03868357
0.00000000
28.27433388
0.00000000
---
((3, -3), 2)
Circle((3, -3), 2)
0.00000000
0.00000000
0.00000000
0.00000000
0.00000000
12.56637061
---
((-1, 0), 1)
Circle((-1, 0), 1)
3.14159265
0.00000000
0.00000000
0.00000000
2.89596536
0.00000000
---
((4, 5), 5)
Circle((4, 5), 5)
0.00000000
78.53981634
0.00000000
78.53981634
0.00000000
0.00000000
---
((2, -4), 2)
Circle((2, -4), 2)
0.00000000
0.00000000
12.56637061
0.00000000
0.77720565
7.02968231
---
((4, 5), 6)
Circle((4, 5), 6)
0.00000000
78.53981634
0.00000000
113.09733553
0.00000000
0.00000000
---
((-2, -2), 3)
Circle((-2, -2), 3)
2.89596536
0.00000000
0.77720565
0.00000000
28.27433388
0.00000000
---
((3, -3), 2)
Circle((3, -3), 2)
0.00000000
0.00000000
7.02968231
0.00000000
0.00000000
12.56637061
Submitting to Coursera#
Because your circle.py
file imports from your point.py
file, you must submit these two files together.