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}