geo/algorithm/skew.rs
1use crate::{AffineOps, AffineTransform, BoundingRect, Coord, CoordFloat, CoordNum, Rect};
2
3/// An affine transformation which skews a geometry, sheared by angles along x and y dimensions.
4///
5/// ## Performance
6///
7/// If you will be performing multiple transformations, like [`Scale`](crate::Scale),
8/// [`Skew`](crate::Skew), [`Translate`](crate::Translate), or [`Rotate`](crate::Rotate), it is more
9/// efficient to compose the transformations and apply them as a single operation using the
10/// [`AffineOps`](crate::AffineOps) trait.
11///
12pub trait Skew<T: CoordNum> {
13 /// An affine transformation which skews a geometry, sheared by a uniform angle along the x and
14 /// y dimensions.
15 ///
16 /// # Examples
17 ///
18 /// ```
19 /// use geo::Skew;
20 /// use geo::{Polygon, polygon};
21 ///
22 /// let square: Polygon = polygon![
23 /// (x: 0., y: 0.),
24 /// (x: 10., y: 0.),
25 /// (x: 10., y: 10.),
26 /// (x: 0., y: 10.)
27 /// ];
28 ///
29 /// let skewed = square.skew(30.);
30 ///
31 /// let expected_output: Polygon = polygon![
32 /// (x: -2.89, y: -2.89),
33 /// (x: 7.11, y: 2.89),
34 /// (x: 12.89, y: 12.89),
35 /// (x: 2.89, y: 7.11)
36 /// ];
37 /// approx::assert_relative_eq!(skewed, expected_output, epsilon = 1e-2);
38 /// ```
39 #[must_use]
40 fn skew(&self, degrees: T) -> Self;
41
42 /// Mutable version of [`skew`](Self::skew).
43 fn skew_mut(&mut self, degrees: T);
44
45 /// An affine transformation which skews a geometry, sheared by an angle along the x and y dimensions.
46 ///
47 /// # Examples
48 ///
49 /// ```
50 /// use geo::Skew;
51 /// use geo::{Polygon, polygon};
52 ///
53 /// let square: Polygon = polygon![
54 /// (x: 0., y: 0.),
55 /// (x: 10., y: 0.),
56 /// (x: 10., y: 10.),
57 /// (x: 0., y: 10.)
58 /// ];
59 ///
60 /// let skewed = square.skew_xy(30., 12.);
61 ///
62 /// let expected_output: Polygon = polygon![
63 /// (x: -2.89, y: -1.06),
64 /// (x: 7.11, y: 1.06),
65 /// (x: 12.89, y: 11.06),
66 /// (x: 2.89, y: 8.94)
67 /// ];
68 /// approx::assert_relative_eq!(skewed, expected_output, epsilon = 1e-2);
69 /// ```
70 #[must_use]
71 fn skew_xy(&self, degrees_x: T, degrees_y: T) -> Self;
72
73 /// Mutable version of [`skew_xy`](Self::skew_xy).
74 fn skew_xy_mut(&mut self, degrees_x: T, degrees_y: T);
75
76 /// An affine transformation which skews a geometry around a point of `origin`, sheared by an
77 /// angle along the x and y dimensions.
78 ///
79 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, in
80 /// which case you can just use [`skew`](Self::skew) or [`skew_xy`](Self::skew_xy), but this method allows you
81 /// to specify any point.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// use geo::Skew;
87 /// use geo::{Polygon, polygon, point};
88 ///
89 /// let square: Polygon = polygon![
90 /// (x: 0., y: 0.),
91 /// (x: 10., y: 0.),
92 /// (x: 10., y: 10.),
93 /// (x: 0., y: 10.)
94 /// ];
95 ///
96 /// let origin = point! { x: 2., y: 2. };
97 /// let skewed = square.skew_around_point(45.0, 10.0, origin);
98 ///
99 /// let expected_output: Polygon = polygon![
100 /// (x: -2., y: -0.353),
101 /// (x: 8., y: 1.410),
102 /// (x: 18., y: 11.41),
103 /// (x: 8., y: 9.647)
104 /// ];
105 /// approx::assert_relative_eq!(skewed, expected_output, epsilon = 1e-2);
106 /// ```
107 #[must_use]
108 fn skew_around_point(&self, degrees_x: T, degrees_y: T, origin: impl Into<Coord<T>>) -> Self;
109
110 /// Mutable version of [`skew_around_point`](Self::skew_around_point).
111 fn skew_around_point_mut(&mut self, degrees_x: T, degrees_y: T, origin: impl Into<Coord<T>>);
112}
113
114impl<T, IR, G> Skew<T> for G
115where
116 T: CoordFloat,
117 IR: Into<Option<Rect<T>>>,
118 G: Clone + AffineOps<T> + BoundingRect<T, Output = IR>,
119{
120 fn skew(&self, degrees: T) -> Self {
121 self.skew_xy(degrees, degrees)
122 }
123
124 fn skew_mut(&mut self, degrees: T) {
125 self.skew_xy_mut(degrees, degrees);
126 }
127
128 fn skew_xy(&self, degrees_x: T, degrees_y: T) -> Self {
129 let origin = match self.bounding_rect().into() {
130 Some(rect) => rect.center(),
131 // Empty geometries have no bounding rect, but in that case
132 // transforming is a no-op anyway.
133 None => return self.clone(),
134 };
135 self.skew_around_point(degrees_x, degrees_y, origin)
136 }
137
138 fn skew_xy_mut(&mut self, degrees_x: T, degrees_y: T) {
139 let origin = match self.bounding_rect().into() {
140 Some(rect) => rect.center(),
141 // Empty geometries have no bounding rect, but in that case
142 // transforming is a no-op anyway.
143 None => return,
144 };
145 self.skew_around_point_mut(degrees_x, degrees_y, origin);
146 }
147
148 fn skew_around_point(&self, xs: T, ys: T, origin: impl Into<Coord<T>>) -> Self {
149 let transform = AffineTransform::skew(xs, ys, origin);
150 self.affine_transform(&transform)
151 }
152
153 fn skew_around_point_mut(&mut self, xs: T, ys: T, origin: impl Into<Coord<T>>) {
154 let transform = AffineTransform::skew(xs, ys, origin);
155 self.affine_transform_mut(&transform);
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::{line_string, BoundingRect, Centroid, LineString};
163
164 #[test]
165 fn skew_linestring() {
166 let ls: LineString<f64> = line_string![
167 (x: 3.0, y: 0.0),
168 (x: 3.0, y: 10.0),
169 ];
170 let origin = ls.bounding_rect().unwrap().centroid();
171 let sheared = ls.skew_around_point(45.0, 45.0, origin);
172 assert_eq!(
173 sheared,
174 line_string![
175 (x: -1.9999999999999991, y: 0.0),
176 (x: 7.999999999999999, y: 10.0)
177 ]
178 );
179 }
180}