geo/algorithm/
scale.rs

1use crate::{AffineOps, AffineTransform, BoundingRect, Coord, CoordFloat, CoordNum, Rect};
2
3/// An affine transformation which scales a geometry up or down by a factor.
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.
11pub trait Scale<T: CoordNum> {
12    /// Scale a geometry from it's bounding box center.
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// use geo::Scale;
18    /// use geo::{LineString, line_string};
19    ///
20    /// let ls: LineString = line_string![(x: 0., y: 0.), (x: 10., y: 10.)];
21    ///
22    /// let scaled = ls.scale(2.);
23    ///
24    /// assert_eq!(scaled, line_string![
25    ///     (x: -5., y: -5.),
26    ///     (x: 15., y: 15.)
27    /// ]);
28    /// ```
29    #[must_use]
30    fn scale(&self, scale_factor: T) -> Self;
31
32    /// Mutable version of [`scale`](Self::scale)
33    fn scale_mut(&mut self, scale_factor: T);
34
35    /// Scale a geometry from it's bounding box center, using different values for `x_factor` and
36    /// `y_factor` to distort the geometry's [aspect ratio](https://en.wikipedia.org/wiki/Aspect_ratio).
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use geo::Scale;
42    /// use geo::{LineString, line_string};
43    ///
44    /// let ls: LineString = line_string![(x: 0., y: 0.), (x: 10., y: 10.)];
45    ///
46    /// let scaled = ls.scale_xy(2., 4.);
47    ///
48    /// assert_eq!(scaled, line_string![
49    ///     (x: -5., y: -15.),
50    ///     (x: 15., y: 25.)
51    /// ]);
52    /// ```
53    #[must_use]
54    fn scale_xy(&self, x_factor: T, y_factor: T) -> Self;
55
56    /// Mutable version of [`scale_xy`](Self::scale_xy).
57    fn scale_xy_mut(&mut self, x_factor: T, y_factor: T);
58
59    /// Scale a geometry around a point of `origin`.
60    ///
61    /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, in
62    /// which case you can just use [`scale`](Self::scale) or [`scale_xy`](Self::scale_xy), but
63    /// this method allows you to specify any point.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use geo::Scale;
69    /// use geo::{LineString, line_string};
70    ///
71    /// let ls: LineString = line_string![(x: 0., y: 0.), (x: 10., y: 10.)];
72    ///
73    /// let scaled = ls.scale_xy(2., 4.);
74    ///
75    /// assert_eq!(scaled, line_string![
76    ///     (x: -5., y: -15.),
77    ///     (x: 15., y: 25.)
78    /// ]);
79    /// ```
80    #[must_use]
81    fn scale_around_point(&self, x_factor: T, y_factor: T, origin: impl Into<Coord<T>>) -> Self;
82
83    /// Mutable version of [`scale_around_point`](Self::scale_around_point).
84    fn scale_around_point_mut(&mut self, x_factor: T, y_factor: T, origin: impl Into<Coord<T>>);
85}
86
87impl<T, IR, G> Scale<T> for G
88where
89    T: CoordFloat,
90    IR: Into<Option<Rect<T>>>,
91    G: Clone + AffineOps<T> + BoundingRect<T, Output = IR>,
92{
93    fn scale(&self, scale_factor: T) -> Self {
94        self.scale_xy(scale_factor, scale_factor)
95    }
96
97    fn scale_mut(&mut self, scale_factor: T) {
98        self.scale_xy_mut(scale_factor, scale_factor);
99    }
100
101    fn scale_xy(&self, x_factor: T, y_factor: T) -> Self {
102        let origin = match self.bounding_rect().into() {
103            Some(rect) => rect.center(),
104            // Empty geometries have no bounding rect, but in that case
105            // transforming is a no-op anyway.
106            None => return self.clone(),
107        };
108        self.scale_around_point(x_factor, y_factor, origin)
109    }
110
111    fn scale_xy_mut(&mut self, x_factor: T, y_factor: T) {
112        let origin = match self.bounding_rect().into() {
113            Some(rect) => rect.center(),
114            // Empty geometries have no bounding rect, but in that case
115            // transforming is a no-op anyway.
116            None => return,
117        };
118        self.scale_around_point_mut(x_factor, y_factor, origin);
119    }
120
121    fn scale_around_point(&self, x_factor: T, y_factor: T, origin: impl Into<Coord<T>>) -> Self {
122        let affineop = AffineTransform::scale(x_factor, y_factor, origin);
123        self.affine_transform(&affineop)
124    }
125
126    fn scale_around_point_mut(&mut self, x_factor: T, y_factor: T, origin: impl Into<Coord<T>>) {
127        let affineop = AffineTransform::scale(x_factor, y_factor, origin);
128        self.affine_transform_mut(&affineop)
129    }
130}