geoarrow/algorithm/geo/
rotate.rs

1use std::sync::Arc;
2
3use crate::algorithm::geo::{AffineOps, Center, Centroid};
4use crate::array::MultiPointArray;
5use crate::array::*;
6use crate::datatypes::{Dimension, GeoDataType};
7use crate::error::Result;
8use crate::trait_::GeometryArrayAccessor;
9use crate::GeometryArrayTrait;
10use arrow_array::{Float64Array, OffsetSizeTrait};
11use geo::AffineTransform;
12
13/// Rotate geometries around a point by an angle, in degrees.
14///
15/// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
16///
17/// ## Performance
18///
19/// If you will be performing multiple transformations, like
20/// [`Scale`](crate::algorithm::geo::Scale), [`Skew`](crate::algorithm::geo::Skew),
21/// [`Translate`](crate::algorithm::geo::Translate), or [`Rotate`](crate::algorithm::geo::Rotate),
22/// it is more efficient to compose the transformations and apply them as a single operation using
23/// the [`AffineOps`](crate::algorithm::geo::AffineOps) trait.
24pub trait Rotate<DegreesT> {
25    type Output;
26
27    /// Rotate a geometry around its [centroid](Centroid) by an angle, in degrees
28    ///
29    /// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use geo::Rotate;
35    /// use geo::line_string;
36    /// use approx::assert_relative_eq;
37    ///
38    /// let line_string = line_string![
39    ///     (x: 0.0, y: 0.0),
40    ///     (x: 5.0, y: 5.0),
41    ///     (x: 10.0, y: 10.0),
42    /// ];
43    ///
44    /// let rotated = line_string.rotate_around_centroid(-45.0);
45    ///
46    /// let expected = line_string![
47    ///     (x: -2.071067811865475, y: 5.0),
48    ///     (x: 5.0, y: 5.0),
49    ///     (x: 12.071067811865476, y: 5.0),
50    /// ];
51    ///
52    /// assert_relative_eq!(expected, rotated);
53    /// ```
54    #[must_use]
55    fn rotate_around_centroid(&self, degrees: &DegreesT) -> Self::Output;
56
57    /// Rotate a geometry around the center of its [bounding
58    /// box](crate::algorithm::geo::BoundingRect) by an angle, in degrees.
59    ///
60    /// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
61    ///
62    #[must_use]
63    fn rotate_around_center(&self, degrees: &DegreesT) -> Self::Output;
64
65    /// Rotate a Geometry around an arbitrary point by an angle, given in degrees
66    ///
67    /// Positive angles are counter-clockwise, and negative angles are clockwise rotations.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use geo::Rotate;
73    /// use geo::{line_string, point};
74    ///
75    /// let ls = line_string![
76    ///     (x: 0.0, y: 0.0),
77    ///     (x: 5.0, y: 5.0),
78    ///     (x: 10.0, y: 10.0)
79    /// ];
80    ///
81    /// let rotated = ls.rotate_around_point(
82    ///     -45.0,
83    ///     point!(x: 10.0, y: 0.0),
84    /// );
85    ///
86    /// assert_eq!(rotated, line_string![
87    ///     (x: 2.9289321881345245, y: 7.071067811865475),
88    ///     (x: 10.0, y: 7.0710678118654755),
89    ///     (x: 17.071067811865476, y: 7.0710678118654755)
90    /// ]);
91    /// ```
92    #[must_use]
93    fn rotate_around_point(&self, degrees: &DegreesT, point: geo::Point) -> Self::Output;
94}
95
96// ┌────────────────────────────────┐
97// │ Implementations for RHS arrays │
98// └────────────────────────────────┘
99
100// Note: this can't (easily) be parameterized in the macro because PointArray is not generic over O
101impl Rotate<Float64Array> for PointArray<2> {
102    type Output = Self;
103
104    fn rotate_around_centroid(&self, degrees: &Float64Array) -> Self {
105        let centroids = self.centroid();
106        let transforms: Vec<AffineTransform> = centroids
107            .iter_geo_values()
108            .zip(degrees.values().iter())
109            .map(|(point, angle)| AffineTransform::rotate(*angle, point))
110            .collect();
111        self.affine_transform(transforms.as_slice())
112    }
113
114    fn rotate_around_center(&self, degrees: &Float64Array) -> Self {
115        let centers = self.center();
116        let transforms: Vec<AffineTransform> = centers
117            .iter_geo_values()
118            .zip(degrees.values().iter())
119            .map(|(point, angle)| AffineTransform::rotate(*angle, point))
120            .collect();
121        self.affine_transform(transforms.as_slice())
122    }
123
124    fn rotate_around_point(&self, degrees: &Float64Array, point: geo::Point) -> Self {
125        let transforms: Vec<AffineTransform> = degrees
126            .values()
127            .iter()
128            .map(|degrees| AffineTransform::rotate(*degrees, point))
129            .collect();
130        self.affine_transform(transforms.as_slice())
131    }
132}
133
134/// Implementation that iterates over geo objects
135macro_rules! iter_geo_impl {
136    ($type:ty) => {
137        impl<O: OffsetSizeTrait> Rotate<Float64Array> for $type {
138            type Output = Self;
139
140            fn rotate_around_centroid(&self, degrees: &Float64Array) -> $type {
141                let centroids = self.centroid();
142                let transforms: Vec<AffineTransform> = centroids
143                    .iter_geo_values()
144                    .zip(degrees.values().iter())
145                    .map(|(point, angle)| AffineTransform::rotate(*angle, point))
146                    .collect();
147                self.affine_transform(transforms.as_slice())
148            }
149
150            fn rotate_around_center(&self, degrees: &Float64Array) -> Self {
151                let centers = self.center();
152                let transforms: Vec<AffineTransform> = centers
153                    .iter_geo_values()
154                    .zip(degrees.values().iter())
155                    .map(|(point, angle)| AffineTransform::rotate(*angle, point))
156                    .collect();
157                self.affine_transform(transforms.as_slice())
158            }
159
160            fn rotate_around_point(&self, degrees: &Float64Array, point: geo::Point) -> Self {
161                let transforms: Vec<AffineTransform> = degrees
162                    .values()
163                    .iter()
164                    .map(|degrees| AffineTransform::rotate(*degrees, point))
165                    .collect();
166                self.affine_transform(transforms.as_slice())
167            }
168        }
169    };
170}
171
172iter_geo_impl!(LineStringArray<O, 2>);
173iter_geo_impl!(PolygonArray<O, 2>);
174iter_geo_impl!(MultiPointArray<O, 2>);
175iter_geo_impl!(MultiLineStringArray<O, 2>);
176iter_geo_impl!(MultiPolygonArray<O, 2>);
177
178// ┌─────────────────────────────────┐
179// │ Implementations for RHS scalars │
180// └─────────────────────────────────┘
181
182// Note: this can't (easily) be parameterized in the macro because PointArray is not generic over O
183impl Rotate<f64> for PointArray<2> {
184    type Output = Self;
185
186    fn rotate_around_centroid(&self, degrees: &f64) -> Self {
187        let centroids = self.centroid();
188        let transforms: Vec<AffineTransform> = centroids
189            .iter_geo_values()
190            .map(|point| AffineTransform::rotate(*degrees, point))
191            .collect();
192        self.affine_transform(transforms.as_slice())
193    }
194
195    fn rotate_around_center(&self, degrees: &f64) -> Self {
196        let centers = self.center();
197        let transforms: Vec<AffineTransform> = centers
198            .iter_geo_values()
199            .map(|point| AffineTransform::rotate(*degrees, point))
200            .collect();
201        self.affine_transform(transforms.as_slice())
202    }
203
204    fn rotate_around_point(&self, degrees: &f64, point: geo::Point) -> Self {
205        let transform = AffineTransform::rotate(*degrees, point);
206        self.affine_transform(&transform)
207    }
208}
209
210/// Implementation that iterates over geo objects
211macro_rules! iter_geo_impl_scalar {
212    ($type:ty) => {
213        impl<O: OffsetSizeTrait> Rotate<f64> for $type {
214            type Output = Self;
215
216            fn rotate_around_centroid(&self, degrees: &f64) -> $type {
217                let centroids = self.centroid();
218                let transforms: Vec<AffineTransform> = centroids
219                    .iter_geo_values()
220                    .map(|point| AffineTransform::rotate(*degrees, point))
221                    .collect();
222                self.affine_transform(transforms.as_slice())
223            }
224
225            fn rotate_around_center(&self, degrees: &f64) -> Self {
226                let centers = self.center();
227                let transforms: Vec<AffineTransform> = centers
228                    .iter_geo_values()
229                    .map(|point| AffineTransform::rotate(*degrees, point))
230                    .collect();
231                self.affine_transform(transforms.as_slice())
232            }
233
234            fn rotate_around_point(&self, degrees: &f64, point: geo::Point) -> Self {
235                let transform = AffineTransform::rotate(*degrees, point);
236                self.affine_transform(&transform)
237            }
238        }
239    };
240}
241
242iter_geo_impl_scalar!(LineStringArray<O, 2>);
243iter_geo_impl_scalar!(PolygonArray<O, 2>);
244iter_geo_impl_scalar!(MultiPointArray<O, 2>);
245iter_geo_impl_scalar!(MultiLineStringArray<O, 2>);
246iter_geo_impl_scalar!(MultiPolygonArray<O, 2>);
247
248impl Rotate<f64> for &dyn GeometryArrayTrait {
249    type Output = Result<Arc<dyn GeometryArrayTrait>>;
250
251    fn rotate_around_centroid(&self, degrees: &f64) -> Self::Output {
252        macro_rules! impl_method {
253            ($method:ident) => {{
254                Arc::new(self.$method().rotate_around_centroid(degrees))
255            }};
256        }
257
258        use Dimension::*;
259        use GeoDataType::*;
260
261        let result: Arc<dyn GeometryArrayTrait> = match self.data_type() {
262            Point(_, XY) => impl_method!(as_point),
263            LineString(_, XY) => impl_method!(as_line_string),
264            LargeLineString(_, XY) => impl_method!(as_large_line_string),
265            Polygon(_, XY) => impl_method!(as_polygon),
266            LargePolygon(_, XY) => impl_method!(as_large_polygon),
267            MultiPoint(_, XY) => impl_method!(as_multi_point),
268            LargeMultiPoint(_, XY) => impl_method!(as_large_multi_point),
269            MultiLineString(_, XY) => impl_method!(as_multi_line_string),
270            LargeMultiLineString(_, XY) => {
271                impl_method!(as_large_multi_line_string)
272            }
273            MultiPolygon(_, XY) => impl_method!(as_multi_polygon),
274            LargeMultiPolygon(_, XY) => impl_method!(as_large_multi_polygon),
275            // Mixed(_, XY) => impl_method!(as_mixed),
276            // LargeMixed(_, XY) => impl_method!(as_large_mixed),
277            // GeometryCollection(_, XY) => impl_method!(as_geometry_collection),
278            // LargeGeometryCollection(_, XY) => {
279            //     impl_method!(as_large_geometry_collection)
280            // }
281            // WKB => impl_method!(as_wkb),
282            // LargeWKB => impl_method!(as_large_wkb),
283            // Rect(XY) => impl_method!(as_rect),
284            _ => todo!("unsupported data type"),
285        };
286
287        Ok(result)
288    }
289
290    fn rotate_around_center(&self, degrees: &f64) -> Self::Output {
291        macro_rules! impl_method {
292            ($method:ident) => {{
293                Arc::new(self.$method().rotate_around_center(degrees))
294            }};
295        }
296
297        use Dimension::*;
298        use GeoDataType::*;
299
300        let result: Arc<dyn GeometryArrayTrait> = match self.data_type() {
301            Point(_, XY) => impl_method!(as_point),
302            LineString(_, XY) => impl_method!(as_line_string),
303            LargeLineString(_, XY) => impl_method!(as_large_line_string),
304            Polygon(_, XY) => impl_method!(as_polygon),
305            LargePolygon(_, XY) => impl_method!(as_large_polygon),
306            MultiPoint(_, XY) => impl_method!(as_multi_point),
307            LargeMultiPoint(_, XY) => impl_method!(as_large_multi_point),
308            MultiLineString(_, XY) => impl_method!(as_multi_line_string),
309            LargeMultiLineString(_, XY) => {
310                impl_method!(as_large_multi_line_string)
311            }
312            MultiPolygon(_, XY) => impl_method!(as_multi_polygon),
313            LargeMultiPolygon(_, XY) => impl_method!(as_large_multi_polygon),
314            // Mixed(_, XY) => impl_method!(as_mixed),
315            // LargeMixed(_, XY) => impl_method!(as_large_mixed),
316            // GeometryCollection(_, XY) => impl_method!(as_geometry_collection),
317            // LargeGeometryCollection(_, XY) => {
318            //     impl_method!(as_large_geometry_collection)
319            // }
320            // WKB => impl_method!(as_wkb),
321            // LargeWKB => impl_method!(as_large_wkb),
322            // Rect(XY) => impl_method!(as_rect),
323            _ => todo!("unsupported data type"),
324        };
325
326        Ok(result)
327    }
328
329    fn rotate_around_point(&self, degrees: &f64, point: geo::Point) -> Self::Output {
330        macro_rules! impl_method {
331            ($method:ident) => {{
332                Arc::new(self.$method().rotate_around_point(degrees, point))
333            }};
334        }
335
336        use Dimension::*;
337        use GeoDataType::*;
338
339        let result: Arc<dyn GeometryArrayTrait> = match self.data_type() {
340            Point(_, XY) => impl_method!(as_point),
341            LineString(_, XY) => impl_method!(as_line_string),
342            LargeLineString(_, XY) => impl_method!(as_large_line_string),
343            Polygon(_, XY) => impl_method!(as_polygon),
344            LargePolygon(_, XY) => impl_method!(as_large_polygon),
345            MultiPoint(_, XY) => impl_method!(as_multi_point),
346            LargeMultiPoint(_, XY) => impl_method!(as_large_multi_point),
347            MultiLineString(_, XY) => impl_method!(as_multi_line_string),
348            LargeMultiLineString(_, XY) => {
349                impl_method!(as_large_multi_line_string)
350            }
351            MultiPolygon(_, XY) => impl_method!(as_multi_polygon),
352            LargeMultiPolygon(_, XY) => impl_method!(as_large_multi_polygon),
353            // Mixed(_, XY) => impl_method!(as_mixed),
354            // LargeMixed(_, XY) => impl_method!(as_large_mixed),
355            // GeometryCollection(_, XY) => impl_method!(as_geometry_collection),
356            // LargeGeometryCollection(_, XY) => {
357            //     impl_method!(as_large_geometry_collection)
358            // }
359            // WKB => impl_method!(as_wkb),
360            // LargeWKB => impl_method!(as_large_wkb),
361            // Rect(XY) => impl_method!(as_rect),
362            _ => todo!("unsupported data type"),
363        };
364
365        Ok(result)
366    }
367}
368
369impl Rotate<Float64Array> for &dyn GeometryArrayTrait {
370    type Output = Result<Arc<dyn GeometryArrayTrait>>;
371
372    fn rotate_around_centroid(&self, degrees: &Float64Array) -> Self::Output {
373        macro_rules! impl_method {
374            ($method:ident) => {{
375                Arc::new(self.$method().rotate_around_centroid(degrees))
376            }};
377        }
378
379        use Dimension::*;
380        use GeoDataType::*;
381
382        let result: Arc<dyn GeometryArrayTrait> = match self.data_type() {
383            Point(_, XY) => impl_method!(as_point),
384            LineString(_, XY) => impl_method!(as_line_string),
385            LargeLineString(_, XY) => impl_method!(as_large_line_string),
386            Polygon(_, XY) => impl_method!(as_polygon),
387            LargePolygon(_, XY) => impl_method!(as_large_polygon),
388            MultiPoint(_, XY) => impl_method!(as_multi_point),
389            LargeMultiPoint(_, XY) => impl_method!(as_large_multi_point),
390            MultiLineString(_, XY) => impl_method!(as_multi_line_string),
391            LargeMultiLineString(_, XY) => {
392                impl_method!(as_large_multi_line_string)
393            }
394            MultiPolygon(_, XY) => impl_method!(as_multi_polygon),
395            LargeMultiPolygon(_, XY) => impl_method!(as_large_multi_polygon),
396            // Mixed(_, XY) => impl_method!(as_mixed),
397            // LargeMixed(_, XY) => impl_method!(as_large_mixed),
398            // GeometryCollection(_, XY) => impl_method!(as_geometry_collection),
399            // LargeGeometryCollection(_, XY) => {
400            //     impl_method!(as_large_geometry_collection)
401            // }
402            // WKB => impl_method!(as_wkb),
403            // LargeWKB => impl_method!(as_large_wkb),
404            // Rect(XY) => impl_method!(as_rect),
405            _ => todo!("unsupported data type"),
406        };
407
408        Ok(result)
409    }
410
411    fn rotate_around_center(&self, degrees: &Float64Array) -> Self::Output {
412        macro_rules! impl_method {
413            ($method:ident) => {{
414                Arc::new(self.$method().rotate_around_center(degrees))
415            }};
416        }
417
418        use Dimension::*;
419        use GeoDataType::*;
420
421        let result: Arc<dyn GeometryArrayTrait> = match self.data_type() {
422            Point(_, XY) => impl_method!(as_point),
423            LineString(_, XY) => impl_method!(as_line_string),
424            LargeLineString(_, XY) => impl_method!(as_large_line_string),
425            Polygon(_, XY) => impl_method!(as_polygon),
426            LargePolygon(_, XY) => impl_method!(as_large_polygon),
427            MultiPoint(_, XY) => impl_method!(as_multi_point),
428            LargeMultiPoint(_, XY) => impl_method!(as_large_multi_point),
429            MultiLineString(_, XY) => impl_method!(as_multi_line_string),
430            LargeMultiLineString(_, XY) => {
431                impl_method!(as_large_multi_line_string)
432            }
433            MultiPolygon(_, XY) => impl_method!(as_multi_polygon),
434            LargeMultiPolygon(_, XY) => impl_method!(as_large_multi_polygon),
435            // Mixed(_, XY) => impl_method!(as_mixed),
436            // LargeMixed(_, XY) => impl_method!(as_large_mixed),
437            // GeometryCollection(_, XY) => impl_method!(as_geometry_collection),
438            // LargeGeometryCollection(_, XY) => {
439            //     impl_method!(as_large_geometry_collection)
440            // }
441            // WKB => impl_method!(as_wkb),
442            // LargeWKB => impl_method!(as_large_wkb),
443            // Rect(XY) => impl_method!(as_rect),
444            _ => todo!("unsupported data type"),
445        };
446
447        Ok(result)
448    }
449
450    fn rotate_around_point(&self, degrees: &Float64Array, point: geo::Point) -> Self::Output {
451        macro_rules! impl_method {
452            ($method:ident) => {{
453                Arc::new(self.$method().rotate_around_point(degrees, point))
454            }};
455        }
456
457        use Dimension::*;
458        use GeoDataType::*;
459
460        let result: Arc<dyn GeometryArrayTrait> = match self.data_type() {
461            Point(_, XY) => impl_method!(as_point),
462            LineString(_, XY) => impl_method!(as_line_string),
463            LargeLineString(_, XY) => impl_method!(as_large_line_string),
464            Polygon(_, XY) => impl_method!(as_polygon),
465            LargePolygon(_, XY) => impl_method!(as_large_polygon),
466            MultiPoint(_, XY) => impl_method!(as_multi_point),
467            LargeMultiPoint(_, XY) => impl_method!(as_large_multi_point),
468            MultiLineString(_, XY) => impl_method!(as_multi_line_string),
469            LargeMultiLineString(_, XY) => {
470                impl_method!(as_large_multi_line_string)
471            }
472            MultiPolygon(_, XY) => impl_method!(as_multi_polygon),
473            LargeMultiPolygon(_, XY) => impl_method!(as_large_multi_polygon),
474            // Mixed(_, XY) => impl_method!(as_mixed),
475            // LargeMixed(_, XY) => impl_method!(as_large_mixed),
476            // GeometryCollection(_, XY) => impl_method!(as_geometry_collection),
477            // LargeGeometryCollection(_, XY) => {
478            //     impl_method!(as_large_geometry_collection)
479            // }
480            // WKB => impl_method!(as_wkb),
481            // LargeWKB => impl_method!(as_large_wkb),
482            // Rect(XY) => impl_method!(as_rect),
483            _ => todo!("unsupported data type"),
484        };
485
486        Ok(result)
487    }
488}