geo/algorithm/
rotate.rs

1use crate::algorithm::{AffineOps, AffineTransform, BoundingRect, Centroid};
2use crate::geometry::*;
3use crate::CoordFloat;
4
5/// Rotate a geometry around a point by an angle, in degrees.
6///
7/// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
8///
9/// ## Performance
10///
11/// If you will be performing multiple transformations, like [`Scale`](crate::Scale),
12/// [`Skew`](crate::Skew), [`Translate`](crate::Translate), or [`Rotate`](crate::Rotate), it is more
13/// efficient to compose the transformations and apply them as a single operation using the
14/// [`AffineOps`](crate::AffineOps) trait.
15pub trait Rotate<T: CoordFloat> {
16    /// Rotate a geometry around its [centroid](Centroid) by an angle, in degrees
17    ///
18    /// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
19    ///
20    /// # Examples
21    ///
22    /// ```
23    /// use geo::Rotate;
24    /// use geo::line_string;
25    /// use approx::assert_relative_eq;
26    ///
27    /// let line_string = line_string![
28    ///     (x: 0.0, y: 0.0),
29    ///     (x: 5.0, y: 5.0),
30    ///     (x: 10.0, y: 10.0),
31    /// ];
32    ///
33    /// let rotated = line_string.rotate_around_centroid(-45.0);
34    ///
35    /// let expected = line_string![
36    ///     (x: -2.071067811865475, y: 5.0),
37    ///     (x: 5.0, y: 5.0),
38    ///     (x: 12.071067811865476, y: 5.0),
39    /// ];
40    ///
41    /// assert_relative_eq!(expected, rotated);
42    /// ```
43    #[must_use]
44    fn rotate_around_centroid(&self, degrees: T) -> Self;
45
46    /// Mutable version of [`Self::rotate_around_centroid`]
47    fn rotate_around_centroid_mut(&mut self, degrees: T);
48
49    /// Rotate a geometry around the center of its [bounding box](BoundingRect) by an angle, in
50    /// degrees.
51    ///
52    /// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
53    ///
54    #[must_use]
55    fn rotate_around_center(&self, degrees: T) -> Self;
56
57    /// Mutable version of [`Self::rotate_around_center`]
58    fn rotate_around_center_mut(&mut self, degrees: T);
59
60    /// Rotate a Geometry around an arbitrary point by an angle, given in degrees
61    ///
62    /// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use geo::Rotate;
68    /// use geo::{line_string, point};
69    ///
70    /// let ls = line_string![
71    ///     (x: 0.0, y: 0.0),
72    ///     (x: 5.0, y: 5.0),
73    ///     (x: 10.0, y: 10.0)
74    /// ];
75    ///
76    /// let rotated = ls.rotate_around_point(
77    ///     -45.0,
78    ///     point!(x: 10.0, y: 0.0),
79    /// );
80    ///
81    /// assert_eq!(rotated, line_string![
82    ///     (x: 2.9289321881345245, y: 7.071067811865475),
83    ///     (x: 10.0, y: 7.0710678118654755),
84    ///     (x: 17.071067811865476, y: 7.0710678118654755)
85    /// ]);
86    /// ```
87    #[must_use]
88    fn rotate_around_point(&self, degrees: T, point: Point<T>) -> Self;
89
90    /// Mutable version of [`Self::rotate_around_point`]
91    fn rotate_around_point_mut(&mut self, degrees: T, point: Point<T>);
92}
93
94#[doc(hidden)]
95#[deprecated(since = "0.23.0", note = "Use `Rotate::rotate_around_point` instead.")]
96pub trait RotatePoint<T: CoordFloat> {
97    fn rotate_around_point(&self, degrees: T, point: Point<T>) -> Self;
98}
99
100#[doc(hidden)]
101#[allow(deprecated)]
102impl<T, G> RotatePoint<T> for G
103where
104    T: CoordFloat,
105    G: Rotate<T>,
106{
107    fn rotate_around_point(&self, degrees: T, point: Point<T>) -> Self {
108        Rotate::rotate_around_point(self, degrees, point)
109    }
110}
111
112impl<G, IP, IR, T> Rotate<T> for G
113where
114    T: CoordFloat,
115    IP: Into<Option<Point<T>>>,
116    IR: Into<Option<Rect<T>>>,
117    G: Clone + Centroid<Output = IP> + BoundingRect<T, Output = IR> + AffineOps<T>,
118{
119    fn rotate_around_centroid(&self, degrees: T) -> Self {
120        let point = match self.centroid().into() {
121            Some(coord) => coord,
122            // geometry was empty, so there's nothing to rotate
123            None => return self.clone(),
124        };
125        Rotate::rotate_around_point(self, degrees, point)
126    }
127
128    fn rotate_around_centroid_mut(&mut self, degrees: T) {
129        let point = match self.centroid().into() {
130            Some(coord) => coord,
131            // geometry was empty, so there's nothing to rotate
132            None => return,
133        };
134        self.rotate_around_point_mut(degrees, point)
135    }
136
137    fn rotate_around_center(&self, degrees: T) -> Self {
138        let point = match self.bounding_rect().into() {
139            Some(rect) => Point(rect.center()),
140            // geometry was empty, so there's nothing to rotate
141            None => return self.clone(),
142        };
143        Rotate::rotate_around_point(self, degrees, point)
144    }
145
146    fn rotate_around_center_mut(&mut self, degrees: T) {
147        let point = match self.bounding_rect().into() {
148            Some(rect) => Point(rect.center()),
149            // geometry was empty, so there's nothing to rotate
150            None => return,
151        };
152        self.rotate_around_point_mut(degrees, point)
153    }
154
155    fn rotate_around_point(&self, degrees: T, point: Point<T>) -> Self {
156        let transform = AffineTransform::rotate(degrees, point);
157        self.affine_transform(&transform)
158    }
159
160    fn rotate_around_point_mut(&mut self, degrees: T, point: Point<T>) {
161        let transform = AffineTransform::rotate(degrees, point);
162        self.affine_transform_mut(&transform)
163    }
164}
165
166#[cfg(test)]
167mod test {
168    use crate::algorithm::Rotate;
169    use crate::geometry::*;
170    use crate::{line_string, point, polygon};
171    use approx::assert_relative_eq;
172
173    #[test]
174    fn test_rotate_around_point() {
175        let p = point!(x: 1.0, y: 5.0);
176        let rotated = p.rotate_around_centroid(30.0);
177        // results agree with Shapely / GEOS
178        assert_eq!(rotated, Point::new(1.0, 5.0));
179    }
180
181    #[test]
182    fn test_rotate_points() {
183        let point = point!(x: 1.0, y: 5.0);
184        let rotated_center = point.rotate_around_center(30.);
185        let rotated_centroid = point.rotate_around_centroid(30.);
186
187        // results agree with Shapely / GEOS
188        // a rotated point should always equal itself
189        assert_eq!(point, rotated_center);
190        assert_eq!(point, rotated_centroid);
191    }
192
193    #[test]
194    fn test_rotate_multipoints() {
195        let multi_points = MultiPoint::new(vec![
196            point!(x: 0., y: 0.),
197            point!(x: 1., y: 1.),
198            point!(x: 2., y: 1.),
199        ]);
200
201        // Results match shapely for `centroid`
202        let expected_for_centroid = MultiPoint::new(vec![
203            point!(x: 0.7642977396044841, y: -0.5118446353109125),
204            point!(x: 0.7642977396044842, y:  0.9023689270621824),
205            point!(x: 1.471404520791032, y:  1.60947570824873),
206        ]);
207        assert_relative_eq!(
208            multi_points.rotate_around_centroid(45.),
209            expected_for_centroid
210        );
211
212        // Results match shapely for `center`
213        let expected_for_center = MultiPoint::new(vec![
214            point!(x: 0.6464466094067262, y: -0.5606601717798212),
215            point!(x: 0.6464466094067263, y: 0.8535533905932737),
216            point!(x: 1.353553390593274, y: 1.560660171779821),
217        ]);
218        assert_relative_eq!(multi_points.rotate_around_center(45.), expected_for_center);
219    }
220
221    #[test]
222    fn test_rotate_linestring() {
223        let linestring = line_string![
224            (x: 0.0, y: 0.0),
225            (x: 5.0, y: 5.0),
226            (x: 5.0, y: 10.0)
227        ];
228
229        // results agree with Shapely / GEOS for `centroid`
230        let rotated_around_centroid = linestring.rotate_around_centroid(-45.0);
231        assert_relative_eq!(
232            rotated_around_centroid,
233            line_string![
234                (x: -2.196699141100894, y: 3.838834764831844),
235                (x: 4.874368670764582, y: 3.838834764831844),
236                (x: 8.40990257669732, y: 7.374368670764582)
237            ]
238        );
239
240        // results agree with Shapely / GEOS for `center`
241        let rotated_around_center = linestring.rotate_around_center(-45.0);
242        assert_relative_eq!(
243            rotated_around_center,
244            line_string![
245                (x: -2.803300858899106, y: 3.232233047033631),
246                (x: 4.267766952966369, y: 3.232233047033632),
247                (x: 7.803300858899107, y: 6.767766952966369)
248            ],
249            epsilon = 1e-12
250        );
251    }
252    #[test]
253    fn test_rotate_polygon() {
254        let poly1 = polygon![
255            (x: 5., y: 1.),
256            (x: 4., y: 2.),
257            (x: 4., y: 3.),
258            (x: 5., y: 4.),
259            (x: 6., y: 4.),
260            (x: 7., y: 3.),
261            (x: 7., y: 2.),
262            (x: 6., y: 1.),
263            (x: 5., y: 1.)
264        ];
265        let rotated = poly1.rotate_around_centroid(-15.0);
266        let correct = polygon![
267            (x: 4.6288085192016855, y: 1.1805207831176578),
268            (x: 3.921701738015137, y: 2.405265654509247),
269            (x: 4.180520783117659, y: 3.3711914807983154),
270            (x: 5.405265654509247, y: 4.0782982619848624),
271            (x: 6.371191480798316, y: 3.819479216882342),
272            (x: 7.0782982619848624, y: 2.594734345490753),
273            (x: 6.819479216882343, y: 1.6288085192016848),
274            (x: 5.594734345490753, y: 0.9217017380151372),
275            (x: 4.6288085192016855, y: 1.1805207831176578)
276        ];
277        // results agree with Shapely / GEOS
278        assert_eq!(rotated, correct);
279    }
280    #[test]
281    fn test_rotate_polygon_holes() {
282        let poly1 = polygon![
283            exterior: [
284                (x: 5.0, y: 1.0),
285                (x: 4.0, y: 2.0),
286                (x: 4.0, y: 3.0),
287                (x: 5.0, y: 4.0),
288                (x: 6.0, y: 4.0),
289                (x: 7.0, y: 3.0),
290                (x: 7.0, y: 2.0),
291                (x: 6.0, y: 1.0),
292                (x: 5.0, y: 1.0)
293            ],
294            interiors: [
295                [
296                    (x: 5.0, y: 1.3),
297                    (x: 5.5, y: 2.0),
298                    (x: 6.0, y: 1.3),
299                    (x: 5.0, y: 1.3),
300                ],
301                [
302                    (x: 5., y: 2.3),
303                    (x: 5.5, y: 3.0),
304                    (x: 6., y: 2.3),
305                    (x: 5., y: 2.3),
306                ],
307            ],
308        ];
309
310        // now rotate around center
311        let center_expected = polygon![
312            exterior: [
313                (x: 4.628808519201685, y: 1.180520783117658),
314                (x: 3.921701738015137, y: 2.405265654509247),
315                (x: 4.180520783117659, y: 3.371191480798315),
316                (x: 5.405265654509247, y: 4.078298261984862),
317                (x: 6.371191480798316, y: 3.819479216882342),
318                (x: 7.078298261984862, y: 2.594734345490753),
319                (x: 6.819479216882343, y: 1.628808519201685),
320                (x: 5.594734345490753, y: 0.9217017380151372),
321                (x: 4.628808519201685, y: 1.180520783117658),
322            ],
323            interiors: [
324                [
325                    (x: 4.706454232732442, y: 1.470298531004379),
326                    (x: 5.37059047744874, y: 2.017037086855466),
327                    (x: 5.67238005902151, y: 1.211479485901858),
328                    (x: 4.706454232732442, y: 1.470298531004379),
329                ],
330                [
331                    (x: 4.965273277834962, y: 2.436224357293447),
332                    (x: 5.62940952255126, y: 2.982962913144534),
333                    (x: 5.931199104124032, y: 2.177405312190926),
334                    (x: 4.965273277834962, y: 2.436224357293447),
335                ],
336            ],
337        ];
338
339        let rotated_around_center = poly1.rotate_around_center(-15.);
340
341        assert_relative_eq!(rotated_around_center, center_expected, epsilon = 1e-12);
342
343        // now rotate around centroid
344        let centroid_expected = polygon![
345            exterior: [
346                (x: 4.615388272418591, y: 1.182287592124891),
347                (x: 3.908281491232044, y: 2.40703246351648),
348                (x: 4.167100536334565, y: 3.372958289805549),
349                (x: 5.391845407726153, y: 4.080065070992097),
350                (x: 6.357771234015222, y: 3.821246025889576),
351                (x: 7.064878015201769, y: 2.596501154497987),
352                (x: 6.806058970099248, y: 1.630575328208918),
353                (x: 5.58131409870766, y: 0.9234685470223708),
354                (x: 4.615388272418591, y: 1.182287592124891),
355            ],
356            interiors: [
357                [
358                    (x: 4.693033985949348, y: 1.472065340011612),
359                    (x: 5.357170230665646, y: 2.0188038958627),
360                    (x: 5.658959812238415, y: 1.213246294909091),
361                    (x: 4.693033985949348, y: 1.472065340011612),
362                ],
363                [
364                    (x: 4.951853031051868, y: 2.43799116630068),
365                    (x: 5.615989275768166, y: 2.984729722151768),
366                    (x: 5.917778857340937, y: 2.179172121198159),
367                    (x: 4.951853031051868, y: 2.43799116630068),
368                ],
369            ],
370        ];
371        let rotated_around_centroid = poly1.rotate_around_centroid(-15.);
372        assert_relative_eq!(rotated_around_centroid, centroid_expected, epsilon = 1e-12);
373    }
374    #[test]
375    fn test_rotate_around_point_arbitrary() {
376        let p = Point::new(5.0, 10.0);
377        let rotated = p.rotate_around_point(-45., Point::new(10., 34.));
378        assert_eq!(rotated, Point::new(-10.506096654409877, 20.564971157455595));
379    }
380    #[test]
381    fn test_rotate_line() {
382        let line0 = Line::from([(0., 0.), (0., 2.)]);
383        let line1 = Line::from([(1., 1.), (-1., 1.)]);
384        assert_relative_eq!(line0.rotate_around_centroid(90.0), line1);
385        assert_relative_eq!(line0.rotate_around_center(90.0), line1);
386    }
387
388    #[test]
389    fn test_rotate_multi_line_string() {
390        let ls1 = line_string![
391            (x: 0., y: 0.),
392            (x: 1., y: 1.),
393            (x: 4., y: 1.),
394        ];
395        let ls2 = line_string![
396            (x: 10., y: 10.),
397            (x: 20., y: 20.),
398            (x: 40., y: 20.)
399        ];
400        let multi_line_string: MultiLineString = MultiLineString::new(vec![ls1, ls2]);
401
402        // Results match with Shapely for `centroid`
403        let expected_around_centroid = MultiLineString::new(vec![
404            line_string![
405                (x: -5.062519283392216, y: 19.72288595632566),
406                (x: -3.648305721019121, y: 19.72288595632566),
407                (x: -1.526985377459479, y: 17.60156561276602)
408            ],
409            line_string![
410                (x: 9.079616340338735, y: 19.72288595632566),
411                (x: 23.22175196406969, y: 19.72288595632566),
412                (x: 37.36388758780063, y: 5.580750332594715)
413            ],
414        ]);
415        assert_relative_eq!(
416            multi_line_string.rotate_around_centroid(-45.),
417            expected_around_centroid,
418            epsilon = 1e-12
419        );
420
421        // Results match with Shapely for `center`
422        let expected_around_center: MultiLineString = MultiLineString::new(vec![
423            line_string![
424                (x: -1.213203435596426, y: 17.07106781186548),
425                (x: 0.2010101267766693, y: 17.07106781186548),
426                (x: 2.322330470336312, y: 14.94974746830583),
427            ],
428            line_string![
429                (x: 12.92893218813452, y: 17.07106781186548),
430                (x: 27.07106781186548, y: 17.07106781186548),
431                (x: 41.21320343559643, y: 2.928932188134528),
432
433            ],
434        ]);
435        assert_relative_eq!(
436            multi_line_string.rotate_around_center(-45.),
437            expected_around_center,
438            epsilon = 1e-12
439        );
440    }
441
442    #[test]
443    fn test_rotate_line_around_point() {
444        let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.));
445        let line1 = Line::new(Point::new(0., 0.), Point::new(-2., 0.));
446        assert_relative_eq!(line0.rotate_around_point(90., Point::new(0., 0.)), line1);
447    }
448
449    #[test]
450    fn test_rotate_multipolygon_around_centroid() {
451        let multipolygon: MultiPolygon = vec![
452            polygon![
453                (x: 0., y: 0.),
454                (x: 10., y: 0.),
455                (x: 10., y: 10.),
456                (x: 0., y: 10.),
457                (x: 0., y: 0.),
458            ],
459            polygon![
460                (x: 0., y: 0.),
461                (x: -10., y: 0.),
462                (x: -10., y: -10.),
463                (x: 0., y: -10.),
464                (x: 0., y: 0.),
465            ],
466        ]
467        .into();
468
469        let expected_centroid: MultiPolygon = vec![
470            polygon![
471                (x: 0., y: 0.),
472                (x: 7.0710678118654755, y: 7.071067811865475),
473                (x: 0., y: 14.142135623730951),
474                (x: -7.071067811865475, y: 7.0710678118654755),
475                (x: 0., y: 0.),
476            ],
477            polygon![
478                (x: 0., y: 0.),
479                (x: -7.0710678118654755, y: -7.071067811865475),
480                (x: 0., y: -14.142135623730951),
481                (x: 7.071067811865475, y: -7.0710678118654755),
482                (x: 0., y: 0.),
483            ],
484        ]
485        .into();
486
487        // results agree with Shapely / GEOS
488        // (relaxing the epsilon a bit)
489        assert_relative_eq!(
490            multipolygon.rotate_around_centroid(45.),
491            expected_centroid,
492            epsilon = 1e-12
493        );
494    }
495
496    #[test]
497    fn test_rotate_multipolygons() {
498        let multipolygon: MultiPolygon = vec![
499            polygon![
500               (x: 1., y: 1. ),
501               (x: 2., y: 1. ),
502               (x: 2., y: 10.),
503               (x: 1., y: 10.),
504               (x: 1., y: 1. ),
505            ],
506            polygon![
507                (x: 10., y:  1.),
508                (x: 12., y:  1.),
509                (x: 12., y:  12.),
510                (x: 10., y:  12.),
511                (x: 10., y:  1.),
512            ],
513        ]
514        .into();
515
516        let expected_center: MultiPolygon = vec![
517            polygon![
518                (x: -0.2360967926537398, y: 2.610912703473988),
519                (x: 0.7298290336353284, y: 2.352093658371467),
520                (x: 3.059200439558015, y: 11.04542609497308),
521                (x: 2.093274613268947, y: 11.3042451400756),
522                (x: -0.2360967926537398, y: 2.610912703473988),
523            ],
524            polygon![
525                (x: 8.457235643947875, y: 0.2815412975513012),
526                (x: 10.38908729652601, y: -0.2360967926537403),
527                (x: 13.23609679265374, y: 10.38908729652601),
528                (x: 11.3042451400756, y: 10.90672538673105),
529                (x: 8.457235643947875, y: 0.2815412975513012),
530            ],
531        ]
532        .into();
533
534        let expected_centroid: MultiPolygon = vec![
535            polygon![
536                (x: -0.1016007672888048, y: 3.05186627999456),
537                (x: 0.8643250590002634, y: 2.793047234892039),
538                (x: 3.19369646492295, y: 11.48637967149365),
539                (x: 2.227770638633882, y: 11.74519871659617),
540                (x: -0.1016007672888048, y: 3.05186627999456),
541            ],
542            polygon![
543                (x: 8.59173166931281, y: 0.7224948740718733),
544                (x: 10.52358332189095, y: 0.2048567838668318),
545                (x: 13.37059281801868, y: 10.83004087304658),
546                (x: 11.43874116544054, y: 11.34767896325162),
547                (x: 8.59173166931281, y: 0.7224948740718733),
548            ],
549        ]
550        .into();
551
552        // results agree with Shapely / GEOS
553        assert_relative_eq!(
554            multipolygon.rotate_around_center(-15.),
555            expected_center,
556            epsilon = 1e-12
557        );
558        assert_relative_eq!(
559            multipolygon.rotate_around_centroid(-15.),
560            expected_centroid,
561            epsilon = 1e-12
562        );
563    }
564
565    #[test]
566    fn test_rotate_empty_geometries_error_gracefully() {
567        // line string
568        let empty_linestring: LineString = line_string![];
569        let rotated_empty_linestring = empty_linestring.rotate_around_centroid(90.);
570        assert_eq!(empty_linestring, rotated_empty_linestring);
571
572        // multi line string
573        let empty_multilinestring: MultiLineString = MultiLineString::new(vec![]);
574        let rotated_empty_multilinestring = empty_multilinestring.rotate_around_centroid(90.);
575        assert_eq!(empty_multilinestring, rotated_empty_multilinestring);
576
577        // polygon
578        let empty_polygon: Polygon<f64> = polygon![];
579        let rotated_empty_polygon = empty_polygon.rotate_around_centroid(90.);
580        assert_eq!(empty_polygon, rotated_empty_polygon);
581
582        // multi polygon
583        let empty_multipolygon: MultiPolygon = Vec::<Polygon<f64>>::new().into();
584        let rotated_empty_multipolygon = empty_multipolygon.rotate_around_centroid(90.);
585        assert_eq!(empty_multipolygon, rotated_empty_multipolygon);
586    }
587}