Title: Connect two points with arcs of their midpoint circle in Python
Two points aren't enough to uniquely define a circle because an infinite number of circles can pass through two points and therefore they can be connected by an infinite number of arcs. If you add extra information, however, two points can uniquely define a circle and hence arcs connecting the points. This example finds the circle that intersects two points and that has a center at the points' midpoint.
The example performs two main tasks: letting the user pick the two points and finding the arcs.
Picking Points
When the program starts, the following code prepares to let the user select two points.
# Watch for mouse clicks.
self.points = []
self.canvas.bind("<Button-1>", self.mouse_down)
This code creates an empty self.points list and then registers to catch mouse button down events on the program's tkinter Canvas widget. The following code executes when you press the mouse button down.
def mouse_down(self, event):
'''Save the point.'''
# If we already have two points, start anew.
if len(self.points) == 2:
self.points = []
# Save this point.
self.points.append((event.x, event.y))
# Update the display.
self.update_display()
This method first checks to see if we already have two points. If so, the user has previously selected two points so the code clears the points list.
The code then adds the new point to the points list and calls update_display to display the points and possibly the arcs connecting them.
Find the Arcs
It's pretty easy to find the arcs connecting the points. First, you find the point in the middle of the two selected points. Then you define a circle using that midpoint as the center and with radius equal to the distance between that point and either of the two selected points. You use the position of the selected points with respect to the circle's center to determine where the arcs start and end.
Here's the code that finds the arcs and displays the points and arcs.
def update_display(self):
'''Draw the points and arcs if defined.'''
# Clear the canvas.
self.canvas.delete(tk.ALL)
# Drawing parameters.
r = 5
colors = ['light green', 'light blue']
# If we have two points, find the circle and its arcs.
if len(self.points) == 2:
# Find the arcs.
center, rect, start_angle1, start_angle2 = \
find_midpoint_arcs(self.points[0], self.points[1])
# Draw the center.
self.canvas.create_oval(
center[0] - r, center[1] - r,
center[0] + r, center[1] + r,
fill='pink')
# Draw the arcs. Note that we negate the angles because
# create_arc is weird and draws angles counterclockwise.
for i, angle in enumerate([start_angle1, start_angle2]):
self.canvas.create_arc(
rect[0], rect[1], rect[2], rect[3],
start=-angle, extent=180,
style=tk.ARC, outline=colors[i], width=5)
# Draw the points.
for i, pt in enumerate(self.points):
self.canvas.create_oval(
pt[0] - r, pt[1] - r,
pt[0] + r, pt[1] + r,
fill=colors[i])
The code first deletes any previously drawn shapes. Next, if we have selected two points, the code calls the find_midpoint_arcs method described shortly to find the two arcs. It draws the arcs' center and the arcs.
The code then draws the selected points whether we have two selected points or not.
The following code shows the find_midpoint_arcs method.
def find_midpoint_arcs(p1, p2):
'''Find the arcs connecting the two points.'''
# Find the center.
cx = (p1[0] + p2[0]) / 2
cy = (p1[1] + p2[1]) / 2
# Find the radius.
radius = math.sqrt((p1[0] - cx) ** 2 + (p1[1] - cy) ** 2)
# Create the bounding rectangle.
x1 = cx - radius
x2 = cx + radius
y1 = cy - radius
y2 = cy + radius
rect = (x1, y1, x2, y2)
# Find the start angles.
start_angle1 = math.degrees(math.atan2(p1[1] - cy, p1[0] - cx))
start_angle2 = (start_angle1 + 180) % 360
# Return the center, rectangle, and start angles.
return (cx, cy), rect, start_angle1, start_angle2
First the code averages the coordinates of the two selected points to find the midpoint halfway between them. It uses that point as the arcs' centers. Next, the program uses the Pythagorean theorem to calculate the distance between the first selected point and the center point, and uses the result as the arcs' radii.
The method then builds a tuple holding the bounding rectangle's minimum and maximum X and Y coordinates. (We'll need that later to draw the arcs.)
The trickiest part of the method is where the code calculates the arcs' start angles. The math.atan2 method takes a vector's Y and X components and returns the vector's angle with respect to the horizontal. The code passes the components for a vector from the center to the first selected point into math.atan2 to get the first arc's starting angle. The math.atan2 method returns the angle in radians and tkinter's arc-drawing method needs degrees, so the code uses math.degrees to convert the result into degrees.
The program calculates the second arc's starting angle by adding 180° to the first angle. (Each arc spans 180°.)
Finally the method returns the arcs' center, bounding rectangle, and start angles.
Look back to the update_display method to see how the program uses those values to draw the arcs.
Conclusion
Although you cannot uniquely define arcs connecting two points, you can define arcs that lie on a circle with center at the points' midpoint. In my next post I'll show another way to define arcs that connect two points. Meanwhile download the example to see additional details and to experiment with it.
|