use num_traits::{Float, FromPrimitive};
use ::{Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
use algorithm::centroid::Centroid;
use algorithm::map_coords::MapCoords;
fn rotation_matrix<T>(angle: T, origin: &Point<T>, points: &[Point<T>]) -> Vec<Point<T>>
where
T: Float,
{
let cos_theta = angle.to_radians().cos();
let sin_theta = angle.to_radians().sin();
let x0 = origin.x();
let y0 = origin.y();
points
.iter()
.map(|point| {
let x = point.x() - x0;
let y = point.y() - y0;
Point::new(
x * cos_theta - y * sin_theta + x0,
x * sin_theta + y * cos_theta + y0,
)
})
.collect::<Vec<_>>()
}
pub trait Rotate<T> {
fn rotate(&self, angle: T) -> Self
where
T: Float;
}
pub trait RotatePoint<T> {
fn rotate_around_point(&self, angle: T, point: &Point<T>) -> Self
where
T: Float;
}
impl<T, G> RotatePoint<T> for G
where
T: Float,
G: MapCoords<T, T, Output = G>,
{
fn rotate_around_point(&self, angle: T, point: &Point<T>) -> Self {
let cos_theta = angle.to_radians().cos();
let sin_theta = angle.to_radians().sin();
let x0 = point.x();
let y0 = point.y();
self.map_coords(&|&(x, y)| {
let x = x - x0;
let y = y - y0;
(
x * cos_theta - y * sin_theta + x0,
x * sin_theta + y * cos_theta + y0,
)
})
}
}
impl<T> Rotate<T> for Point<T>
where
T: Float,
{
fn rotate(&self, angle: T) -> Self {
rotation_matrix(angle, &self.centroid(), &[*self])[0]
}
}
impl<T> Rotate<T> for Line<T>
where
T: Float,
{
fn rotate(&self, angle: T) -> Self {
let pts = vec![self.start, self.end];
let rotated = rotation_matrix(angle, &self.centroid(), &pts);
Line::new(rotated[0], rotated[1])
}
}
impl<T> Rotate<T> for LineString<T>
where
T: Float,
{
fn rotate(&self, angle: T) -> Self {
LineString(rotation_matrix(angle, &self.centroid().unwrap(), &self.0))
}
}
impl<T> Rotate<T> for Polygon<T>
where
T: Float + FromPrimitive,
{
fn rotate(&self, angle: T) -> Self {
let centroid = if self.interiors.is_empty() {
self.centroid().unwrap()
} else {
self.exterior.centroid().unwrap()
};
Polygon::new(
LineString(rotation_matrix(angle, ¢roid, &self.exterior.0)),
self.interiors
.iter()
.map(|ring| LineString(rotation_matrix(angle, ¢roid, &ring.0)))
.collect(),
)
}
}
impl<T> Rotate<T> for MultiPolygon<T>
where
T: Float + FromPrimitive,
{
fn rotate(&self, angle: T) -> Self {
MultiPolygon(self.0.iter().map(|poly| poly.rotate(angle)).collect())
}
}
impl<T> Rotate<T> for MultiLineString<T>
where
T: Float + FromPrimitive,
{
fn rotate(&self, angle: T) -> Self {
MultiLineString(self.0.iter().map(|ls| ls.rotate(angle)).collect())
}
}
impl<T> Rotate<T> for MultiPoint<T>
where
T: Float + FromPrimitive,
{
fn rotate(&self, angle: T) -> Self {
MultiPoint(self.0.iter().map(|p| p.rotate(angle)).collect())
}
}
#[cfg(test)]
mod test {
use ::{LineString, Point, Polygon};
use super::*;
#[test]
fn test_rotate_around_point() {
let p = Point::new(1.0, 5.0);
let rotated = p.rotate(30.0);
assert_eq!(rotated, Point::new(1.0, 5.0));
}
#[test]
fn test_rotate_linestring() {
let mut vec = Vec::new();
vec.push(Point::new(0.0, 0.0));
vec.push(Point::new(5.0, 5.0));
vec.push(Point::new(10.0, 10.0));
let linestring = LineString(vec);
let rotated = linestring.rotate(-45.0);
let mut correct = Vec::new();
correct.push(Point::new(-2.0710678118654755, 5.0));
correct.push(Point::new(5.0, 5.0));
correct.push(Point::new(12.071067811865476, 5.0));
let correct_ls = LineString(correct);
assert_eq!(rotated, correct_ls);
}
#[test]
fn test_rotate_polygon() {
let points_raw = vec![
(5., 1.),
(4., 2.),
(4., 3.),
(5., 4.),
(6., 4.),
(7., 3.),
(7., 2.),
(6., 1.),
(5., 1.),
];
let points = points_raw
.iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>();
let poly1 = Polygon::new(LineString(points), vec![]);
let rotated = poly1.rotate(-15.0);
let correct_outside = vec![
(4.628808519201685, 1.1805207831176578),
(3.921701738015137, 2.405265654509247),
(4.180520783117657, 3.3711914807983154),
(5.405265654509247, 4.0782982619848624),
(6.371191480798315, 3.819479216882342),
(7.0782982619848624, 2.594734345490753),
(6.819479216882343, 1.6288085192016848),
(5.594734345490753, 0.9217017380151371),
(4.628808519201685, 1.1805207831176578),
];
let correct = Polygon::new(
LineString(
correct_outside
.iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>(),
),
vec![],
);
assert_eq!(rotated, correct);
}
#[test]
fn test_rotate_polygon_holes() {
let ls1 = LineString(vec![
Point::new(5.0, 1.0),
Point::new(4.0, 2.0),
Point::new(4.0, 3.0),
Point::new(5.0, 4.0),
Point::new(6.0, 4.0),
Point::new(7.0, 3.0),
Point::new(7.0, 2.0),
Point::new(6.0, 1.0),
Point::new(5.0, 1.0),
]);
let ls2 = LineString(vec![
Point::new(5.0, 1.3),
Point::new(5.5, 2.0),
Point::new(6.0, 1.3),
Point::new(5.0, 1.3),
]);
let ls3 = LineString(vec![
Point::new(5., 2.3),
Point::new(5.5, 3.0),
Point::new(6., 2.3),
Point::new(5., 2.3),
]);
let poly1 = Polygon::new(ls1, vec![ls2, ls3]);
let rotated = poly1.rotate(-15.0);
let correct_outside = vec![
(4.628808519201685, 1.180520783117658),
(3.921701738015137, 2.4052656545092472),
(4.180520783117657, 3.3711914807983154),
(5.405265654509247, 4.078298261984863),
(6.371191480798315, 3.8194792168823426),
(7.0782982619848624, 2.594734345490753),
(6.819479216882343, 1.628808519201685),
(5.594734345490753, 0.9217017380151373),
(4.628808519201685, 1.180520783117658),
].iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>();
let correct_inside = vec![
(4.706454232732441, 1.4702985310043786),
(5.37059047744874, 2.017037086855466),
(5.672380059021509, 1.2114794859018578),
(4.706454232732441, 1.4702985310043786),
].iter()
.map(|e| Point::new(e.0, e.1))
.collect::<Vec<_>>();
assert_eq!(rotated.exterior.0, correct_outside);
assert_eq!(rotated.interiors[0].0, correct_inside);
}
#[test]
fn test_rotate_around_point_arbitrary() {
let p = Point::new(5.0, 10.0);
let rotated = p.rotate_around_point(-45., &Point::new(10., 34.));
assert_eq!(rotated, Point::new(-10.506096654409877, 20.564971157455595));
}
#[test]
fn test_rotate_line() {
let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.));
let line1 = Line::new(Point::new(1., 0.9999999999999999), Point::new(-1., 1.));
assert_eq!(line0.rotate(90.), line1);
}
#[test]
fn test_rotate_line_around_point() {
let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.));
let line1 = Line::new(
Point::new(0., 0.),
Point::new(-2., 0.00000000000000012246467991473532),
);
assert_eq!(line0.rotate_around_point(90., &Point::new(0., 0.)), line1);
}
}