68

If you had a point (in 2d), how could you rotate that point by degrees around the other point (the origin) in python?

You might, for example, tilt the first point around the origin by 10 degrees.

Basically you have one point PointA and origin that it rotates around. The code could look something like this:

PointA=(200,300)
origin=(100,100)

NewPointA=rotate(origin,PointA,10) #The rotate function rotates it by 10 degrees
user2592835
  • 1,547
  • 5
  • 18
  • 26
  • 5
    This is in C++ but is the exact same question https://stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-2d – Cory Kramer Dec 19 '15 at 15:44

7 Answers7

127

The following rotate function performs a rotation of the point point by the angle angle (counterclockwise, in radians) around origin, in the Cartesian plane, with the usual axis conventions: x increasing from left to right, y increasing vertically upwards. All points are represented as length-2 tuples of the form (x_coord, y_coord).

import math

def rotate(origin, point, angle):
    """
    Rotate a point counterclockwise by a given angle around a given origin.

    The angle should be given in radians.
    """
    ox, oy = origin
    px, py = point

    qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
    qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
    return qx, qy

If your angle is specified in degrees, you can convert it to radians first using math.radians. For a clockwise rotation, negate the angle.

Example: rotating the point (3, 4) around an origin of (2, 2) counterclockwise by an angle of 10 degrees:

>>> point = (3, 4)
>>> origin = (2, 2)
>>> rotate(origin, point, math.radians(10))
(2.6375113976783475, 4.143263683691346)

Note that there's some obvious repeated calculation in the rotate function: math.cos(angle) and math.sin(angle) are each computed twice, as are px - ox and py - oy. I leave it to you to factor that out if necessary.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
  • this is a great solution. What would change with this if you wanted to transpose the direction of the Y axis (y increasing vertically downwards)? Thank you. – sosukeinu Mar 20 '17 at 01:45
  • Using this function I sometimes get negative coordinates when my original coordinates are within [0,127] and I set the rotation center as (63.5,63.5). Is this due to rounding errors? How to prevent it? – Simon Hessner Oct 14 '19 at 14:29
  • 1
    @SimonH: Possibly rounding errors; possibly just geometry. If you take the square [0, 127] x [0, 127] and rotate it by 10 degrees (say) about (63.5, 63.5), then bits of the rotated square _will_ be outside the 1st quadrant. If your points are within the *disk* radius 63.5 centered at (63.5, 63.5), then yes, mathematically you shouldn't see results with negative coordinates. If you're seeing negative values that are very close to zero, then yes, rounding error is likely to blame. – Mark Dickinson Oct 14 '19 at 15:08
  • Thanks! I figured it out. The problem was not directly related to the rotation but to a scaling that I performe before the rotation – Simon Hessner Oct 14 '19 at 19:34
  • I tried this with rotating multiple rectangular and triangular planes around the origin. It works, but the shapes are distorted (i.e. a rectangle becomes a parallelogram) – L. van Agtmaal Mar 04 '22 at 08:31
  • 1
    @L.vanAgtmaal: I suggest you ask a new question and provide the details there. There are all sorts of things that could possibly have gone wrong in implementation (or even in the way that you're plotting - e.g., if the aspect ratio of the plot is off, that would cause a rectangle to appear as a parallelogram). – Mark Dickinson Mar 04 '22 at 08:49
  • It was indeed the aspect ratio, I realised that a few hours after commenting this. Sorry for the trouble! – L. van Agtmaal May 25 '22 at 13:23
42

An option to rotate a point by some degrees about another point is to use numpy instead of math. This allows to easily generalize the function to take any number of points as input, which might e.g. be useful when rotating a polygon.

import numpy as np

def rotate(p, origin=(0, 0), degrees=0):
    angle = np.deg2rad(degrees)
    R = np.array([[np.cos(angle), -np.sin(angle)],
                  [np.sin(angle),  np.cos(angle)]])
    o = np.atleast_2d(origin)
    p = np.atleast_2d(p)
    return np.squeeze((R @ (p.T-o.T) + o.T).T)


points=[(200, 300), (100, 300)]
origin=(100,100)

new_points = rotate(points, origin=origin, degrees=10)
print(new_points)
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
9
import math

def rotate(x,y,xo,yo,theta): #rotate x,y around xo,yo by theta (rad)
    xr=math.cos(theta)*(x-xo)-math.sin(theta)*(y-yo)   + xo
    yr=math.sin(theta)*(x-xo)+math.cos(theta)*(y-yo)  + yo
    return [xr,yr]
Gabriel Eng
  • 321
  • 3
  • 3
5

After going through a lot of code and repositories. This function worked best for me. Also it is efficient as it calculates sine and cosine values only once.

import numpy as np
def rotate(point, origin, degrees):
    radians = np.deg2rad(degrees)
    x,y = point
    offset_x, offset_y = origin
    adjusted_x = (x - offset_x)
    adjusted_y = (y - offset_y)
    cos_rad = np.cos(radians)
    sin_rad = np.sin(radians)
    qx = offset_x + cos_rad * adjusted_x + sin_rad * adjusted_y
    qy = offset_y + -sin_rad * adjusted_x + cos_rad * adjusted_y
    return qx, qy
an0nym0use
  • 311
  • 1
  • 4
  • 8
5

This is easy if you represent your points as complex numbers and use the exp function with an imaginary argument (which is equivalent to the cos/sin operations shown in the other answers, but is easier to write and remember). Here's a function that rotates any number of points about the chosen origin:

import numpy as np

def rotate(points, origin, angle):
    return (points - origin) * np.exp(complex(0, angle)) + origin

To rotate a single point (x1,y1) about the origin (x0,y0) with an angle in degrees, you could call the function with these arguments:

points = complex(x1,y1)
origin = complex(x0,y0)
angle = np.deg2rad(degrees)

To rotate multiple points (x1,y1), (x2,y2), ..., use:

points = np.array([complex(x1,y1), complex(x2,y2), ...])

An example with a single point (200,300) rotated 10 degrees about (100,100):

>>> new_point = rotate(complex(200,300), complex(100,100), np.deg2rad(10))
>>> new_point
(163.75113976783473+314.3263683691346j)
>>> (new_point.real, new_point.imag)
(163.75113976783473, 314.3263683691346)
Ovaflo
  • 624
  • 6
  • 13
1

The below script was best for me.

from math import radians, sin, cos

def rotate_point_wrt_center(point_to_be_rotated, angle, center_point = (0,0)):
        
    angle = radians(angle)
        
    xnew = cos(angle)*(point_to_be_rotated[0] - center_point[0]) - sin(angle)*(point_to_be_rotated[1] - center_point[1]) + center_point[0]
    ynew = sin(angle)*(point_to_be_rotated[0] - center_point[0]) + cos(angle)*(point_to_be_rotated[1] - center_point[1]) + center_point[1]
    
    return (round(xnew,2),round(ynew,2))

For example: if you want to rotate the point (1,1) (blue dot) by 45° about the point (-1,-1) (red cross), the function gives (1.83, -1.0) (red circle).

>>> rotate_point_wrt_center(point_to_be_rotated = (1,1), angle = -45, center_point = (-1,-1)) # angle is negative to indicate clock-wise rotation.
>>> (1.83, -1.0)

image_rotation_example

Leon
  • 73
  • 1
  • 1
  • 7
1

This is just a more performant recap of Mark Dickinson's answer:

  • allows rotation of many points at once without external libraries
  • efficient, because all values are computed at most once and no temporary list is created
  • rotation angle is given in degrees, which in many cases is more practical
  • rotation about exact angles 90, 180, 270 is computed without numerical approximation (i.e. (1,0) rotated by 90° becomes (0,1) not (6.123233995736766e-17,1))
  • the function returns what it gets, that is a single point is returned as such and a sequence of points as a tuple (if you want to always return a tuple, which might be desirable in some cases, just use return ret)
import math
def rotate_points(*points,angle=0,center=(0,0)):
    '''
    Rotate one or more 2D points counterclockwise by a given angle (in degrees) around a given center.
    '''
    cx,cy = center
    angle = angle % 360
    ang_rad = math.radians(angle)
    cos_ang,sin_ang = (0,1) if angle==90 else (-1,0) if angle==180 else (0,-1) if angle==270 else (math.cos(ang_rad),math.sin(ang_rad))
    ret = tuple((cx+cos_ang*dx-sin_ang*dy,cy+sin_ang*dx+cos_ang*dy) for dx,dy in ((x-cx,y-cy) for x,y in points))
    return ret if len(ret)>1 else ret[0] # a single point is returned as such and a sequence of points as a tuple

Usage:

>>> rotate_points((0,0),(1,0),(-1,1),angle=45,center=(1,1))
((1.0, -0.41421356237309515), (1.7071067811865475, 0.2928932188134524), (-0.41421356237309515, -0.41421356237309515))

You can make the function even more efficient by returning a generator (delete keyword tuple from last but one line), if that is enough for you.

mmj
  • 5,514
  • 2
  • 44
  • 51