12

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])
martineau
  • 119,623
  • 25
  • 170
  • 301
adchilds
  • 963
  • 2
  • 9
  • 22

2 Answers2

25

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

    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2

In other words it's just the average, or arithmetic mean, of the two pairs of x and y coordinate values.

For a multi-segmented line, or polyline, its 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.

The general formulas to rotate a 2D point (x,y) θ radians around the origin (0,0) are:

    x′ = x * cos(θ) - y * sin(θ)
    y′ = x * sin(θ) + y * cos(θ)

To perform a rotation about a different center (cx, cy), the x and y values of the point need to be adjusted by first subtracting the coordinate of the desired center of rotation from the point's coordinate, which has the effect of moving (known in geometry as translating) it is expressed mathematically like this:

    tx = x - cx
    ty = y - cy

then rotating this intermediate point by the angle desired, and finally adding the x and y values of the point of rotation back to the x and y of each coordinate. In geometric terms, it's the following sequence of operations:  Tʀᴀɴsʟᴀᴛᴇ ─► Rᴏᴛᴀᴛᴇ ─► Uɴᴛʀᴀɴsʟᴀᴛᴇ.

This concept can be extended to allow rotating a whole polyline about any arbitrary point—such as its own logical center—by just applying the math described to each point of each line segment within it.

To simplify implementation of this computation, the numerical result of all three sets of calculations can be combined and expressed with a pair of mathematical formulas which perform them all simultaneously. So a new point (x′,y′) can be obtained by rotating an existing point (x,y), θ radians around the point (cx, cy) by using:

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

Incorporating this mathematical/geometrical concept 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])
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Your code is wrong - you need to add the average _length_ to the initial coordinates. – sdasdadas Feb 12 '13 at 21:41
  • @martineau: Take the points (1, 1) and (4, 4), your algorithm would give a center at (1.5, 1.5). – sdasdadas Feb 12 '13 at 21:44
  • Voting up for translation / rotation though. :D – sdasdadas Feb 12 '13 at 21:47
  • So rather than rotating around the center of each individual line in my polyline, I should rotate around the "center" point of the polyline as a whole? – adchilds Feb 12 '13 at 21:50
  • Yes, if that's the point about which you want it to rotate -- it can be any point you want. I've also corrected my math for the first part of my answer and added it back. – martineau Feb 12 '13 at 21:59
  • Yes, thank you. I need to rotate the polyline as a whole so I should be rotating around the center point of the polyline. – adchilds Feb 12 '13 at 22:01
  • adchilds: You can rotate the polyline as a whole about any point you want. The "center point" I describe how to calculate might not be on any line segment of the polyline -- which is fine mathematically, but might not be what you desire. – martineau Feb 12 '13 at 22:04
  • I got your code to work, but it seems my lines are getting smaller and smaller the more I rotate. Is there a way to compensate for this? – firelynx Sep 23 '16 at 19:57
  • @firelynx: The mathematical equations given are exact, but unfortunately computers are not, especially doing float point math. I would expect the points of a polyline to "wander" at bit after many successive rotations, but not necessarily in such a way that would make them regularly all appear to get shorter — so something else you're doing must be causing that. One way to avoid accumulating error would be to never change the values of the actual points, and instead just apply a larger amount of rotation to them each time to obtain the object's current position. Some CAD systems work that way. – martineau Sep 23 '16 at 20:20
  • @martineau It definitely comes from multiple consecutive rotations. I now keep a copy of the original coordinates along with the rotated ones and update the rotated ones from the original ones each time. Works perfectly. I was just hoping to not have to store the same data twice. – firelynx Sep 24 '16 at 11:04
  • @firelynx: Another approach that would at least mitigate the problem is to do higher precision float-point math. To do that you could use the built-in `decimal` module which represents floating-point numbers exactly and the "exactness carries over into arithmetic" according to the docs. The [**Recipes**](https://docs.python.org/2/library/decimal.html#recipes) section of its documentation has `cos()` and `sin()` functions. There's also a third-party module named [`mpmath`](http://mpmath.org) which supports arbitrary precision floating-point arithmetic (and provides the trigonometric functions). – martineau Sep 24 '16 at 15:49
  • @firelynx: One other related thought. Are you storing the rotated x, y coordinates as integers? That would definitely cause a great deal of error to accumulate rapidly. – martineau Sep 24 '16 at 15:54
  • As a side note, this code rotates the point clockwise. For an angle to be positive for counter-clockwise rotation, code in theta to be negative – philm Apr 11 '17 at 17:32
  • @philm: I think a better way to express it might be: Positive angles will rotate the geometry clockwise (around the center point in this case), and negative ones will rotate it counter-clockwise. – martineau Apr 11 '17 at 18:23
2

Your center point is going to be:

centerX = (x2 - x1) / 2 + x1
centerY = (y2 - y1) / 2 + y1

because you take half the length (x2 - x1) / 2 and add it to where your line starts to get to the middle.

As an exercise, take two lines:

line1 = (0, 0) -> (5, 5)
then: |x1 - x2| = 5, when the center x value is at 2.5.

line2 = (2, 2) -> (7, 7)
then: |x1 - x2| = 5, which can't be right because that's the center for
the line that's parallel to it but shifted downwards and to the left
sdasdadas
  • 23,917
  • 20
  • 63
  • 148
  • Thanks, this seems to have fixed the issue of rotation; however, I've still got to connect each individual line together correctly (they individually rotate in place currently). Albeit, this fixes the problem I was most concerned with. Thank you. – adchilds Feb 12 '13 at 21:31
  • @adchilds: If you want to rotate all your lines at once (I assume a polyline is sort of like a polygon?) then you need to find the center of mass of the polyline and rotate all the points of the lines around that center. – sdasdadas Feb 12 '13 at 21:34
  • @sdasdadas: (x2 - x1) / 2 + x1 is just (x1 + x2)/2 – bJust Feb 06 '20 at 08:51