import unittest
import math
from geometry_msgs.msg import Point, Quaternion
from typing import Tuple
def find_goal_heading(p1: Point, p2: Point) -> float:
"""
Given two points, find the heading necessary to aim at the second
point from the first point.
"""
return math.atan2(p2.y - p1.y, p2.x - p1.x)
def find_euclidean_distance(p1: Point, p2: Point) -> float:
"""
Given two points, find the distance between them.
"""
return ((p2.x - p1.x)**2 + (p2.y - p1.y)**2 + (p2.z - p1.z)**2)**(1/2)
def find_roll_pitch_yaw(orientation: Quaternion) -> Tuple[float, float, float]:
"""
Given a Quaternion, this returns the following in order:
- roll: Rotation around the robot's x axis
- Since the x axis is the direction of forward motion,
this is the amount of left/right tilt
- pitch: Rotation around the robot's y axis
- Since the y axis is perpendicular to forward motion,
this is the amount of forward/backward tilt
- yaw: Rotation around the robot's z axis
- Since the z axis is perpendicular to the ground,
this is the robot's orientation on the ground.
"""
q1, q2, q3, q0 = orientation.x, orientation.y, orientation.z, orientation.w
roll = math.atan2(2 * (q0 * q1 + q2 * q3), q0**2 - q1**2 - q2**2 + q3**2)
pitch = math.asin(2 * (q0 * q2 - q1 * q3))
yaw = math.atan2(2 * (q0 * q3 + q1 * q2), q0**2 + q1**2 - q2**2 - q3**2)
return roll, pitch, yaw
def find_angle_diff(angle1: float, angle2: float) -> float:
"""
Find the shortest difference between two angles.
Parameters should be in radians.
"""
return find_normalized_angle(angle1 - angle2)
def find_normalized_angle(angle: float) -> float:
"""
Ensure that the angle in radians lies between -math.pi and math.pi
"""
angle = angle % (2 * math.pi)
if angle > math.pi:
angle -= 2 * math.pi
return angle
class OdometryMathTest(unittest.TestCase):
def test_distance(self):
for a, b, c in [(3, 4, 5), (5, 12, 13), (7, 24, 25), (8, 15, 17), (9, 40, 41)]:
p1 = Point()
p2 = Point()
p1.x = a
p1.y = b
self.assertEqual(c, find_euclidean_distance(p1, p2))
def test_normalized_angle(self):
for theta, normed in [(2 * math.pi, 0.0), (3/2 * math.pi, -math.pi/2), (9 * math.pi, math.pi)]:
self.assertEqual(normed, find_normalized_angle(theta))
def test_angle_diff(self):
for theta1, theta2, diff in [
( 3/4 * math.pi, 1/4 * math.pi, 1/2 * math.pi), #1
( 1/4 * math.pi, 3/4 * math.pi, -1/2 * math.pi), #2
(-1/3 * math.pi, 1/3 * math.pi, -2/3 * math.pi), #3
(15/8 * math.pi, 1/8 * math.pi, -1/4 * math.pi), #4
( 1/8 * math.pi, 15/8 * math.pi, 1/4 * math.pi), #5
( 9/2 * math.pi, -1/2 * math.pi, math.pi), #6
]:
self.assertAlmostEqual(diff, find_angle_diff(theta1, theta2), places=5)
def test_roll_pitch_yaw(self):
for x, y, z, w, rpw in [
(-0.0031924284994602203, 0.005276054609566927, -0.8277794122695923, 0.561019778251648,
(-0.012317164052438935, 0.0006346888584906615, -1.9503363787472408)),
(-0.002063487656414509, 0.0034074627328664064, -0.9837535619735718, 0.17948013544082642,
(-0.007445015587338619, -0.002836786557391314, -2.7806632489220133))
]:
q = Quaternion()
q.x, q.y, q.z, q.w = x, y, z, w
roll, pitch, yaw = find_roll_pitch_yaw(q)
print(roll, pitch, yaw)
self.assertAlmostEqual(rpw[0], roll, places=5)
self.assertAlmostEqual(rpw[1], pitch, places=5)
self.assertAlmostEqual(rpw[2], yaw, places=5)
if __name__ == '__main__':
unittest.main()