adchilds adchilds - 5 months ago 44
Python Question

Rotate line around center point given two vertices

I've been trying to rotate a bunch of lines by 90 degrees (that together form a polyline). Each line contains two vertices, say (x1, y1) and (x2, y2). What I'm currently trying to do is rotate around the center point of the line, given center points |x1 - x2| and |y1 - y2|. For some reason (I'm not very mathematically savvy) I can't get the lines to rotate correctly.

Could someone verify that the math here is correct? I'm thinking that it could be correct, however, when I set the line's vertices to the new rotated vertices, the next line may not be grabbing the new (x2, y2) vertex from the previous line, causing the lines to rotate incorrectly.

Here's what I've written:

def rotate_lines(self, deg=-90):
# Convert from degrees to radians
theta = math.radians(deg)

for pl in self.polylines:
self.curr_pl = pl
for line in pl.lines:
# Get the vertices of the line
# (px, py) = first vertex
# (ox, oy) = second vertex
px, ox = line.get_xdata()
py, oy = line.get_ydata()

# Get the center of the line
cx = math.fabs(px-ox)
cy = math.fabs(py-oy)

# Rotate line around center point
p1x = cx - ((px-cx) * math.cos(theta)) - ((py-cy) * math.sin(theta))
p1y = cy - ((px-cx) * math.sin(theta)) + ((py-cy) * math.cos(theta))

p2x = cx - ((ox-cx) * math.cos(theta)) - ((oy-cy) * math.sin(theta))
p2y = cy - ((ox-cx) * math.sin(theta)) + ((oy-cy) * math.cos(theta))

self.curr_pl.set_line(line, [p1x, p2x], [p1y, p2y])


The coordinates of the center point of a line segment between points (x1,y1) and (x2,y2) is:

center_x = (x1 + x2) / 2
center_y = (y1 + y2) / 2

i.e. It's just the average, or arithmetic mean, of the x and y coordinate values.

For a multi-segmented line, or polyline, the logical center point's x and y coordinates are just the corresponding average of x and y values of all the points. An average is just the sum of the values divided by the number of them.

To rotate a polyline about any point, including its own center, first subtract the x and y value of the point of rotation from each of its coordinates, rotate the intermediate result by the angle desired, then add the x and y value of the point of rotation back to each point to get the final result. Translate → Rotate → Untranslate.

The numerical result of these three calculations can be combined and expressed with a pair of formulas which perform them all simultaneously. So, the mathematical formulas to rotate the point (x,y) θ radians around the point (cx, cy) are:

x_new = (  (x - cx) * cos(θ) + (y - cy) * sin(θ) ) + cx
y_new = ( -(x - cx) * sin(θ) + (y - cy) * cos(θ) ) + cy

Incorporating the math concepts into your function produces the following:

from math import sin, cos, radians

def rotate_lines(self, deg=-90):
    """ Rotate self.polylines the given angle about their centers. """
    theta = radians(deg)  # Convert angle from degrees to radians
    cosang, sinang = cos(theta), sin(theta)

    for pl in self.polylines:
        # Find logical center (avg x and avg y) of entire polyline
        n = len(pl.lines)*2  # Total number of points in polyline
        cx = sum(sum(line.get_xdata()) for line in pl.lines) / n
        cy = sum(sum(line.get_ydata()) for line in pl.lines) / n

        for line in pl.lines:
            # Retrieve vertices of the line
            x1, x2 = line.get_xdata()
            y1, y2 = line.get_ydata()

            # Rotate each around whole polyline's center point
            tx1, ty1 = x1-cx, y1-cy
            p1x = ( tx1*cosang + ty1*sinang) + cx
            p1y = (-tx1*sinang + ty1*cosang) + cy
            tx2, ty2 = x2-cx, y2-cy
            p2x = ( tx2*cosang + ty2*sinang) + cx
            p2y = (-tx2*sinang + ty2*cosang) + cy

            # Replace vertices with updated values
            pl.set_line(line, [p1x, p2x], [p1y, p2y])