geo/algorithm/affine_ops.rs
1use crate::{Coord, CoordFloat, CoordNum, MapCoords, MapCoordsInPlace};
2use std::fmt;
3
4/// Apply an [`AffineTransform`] like [`scale`](AffineTransform::scale),
5/// [`skew`](AffineTransform::skew), or [`rotate`](AffineTransform::rotate) to a
6/// [`Geometry`](crate::geometry::Geometry).
7///
8/// Multiple transformations can be composed in order to be efficiently applied in a single
9/// operation. See [`AffineTransform`] for more on how to build up a transformation.
10///
11/// If you are not composing operations, traits that leverage this same machinery exist which might
12/// be more readable. See: [`Scale`](crate::algorithm::Scale),
13/// [`Translate`](crate::algorithm::Translate), [`Rotate`](crate::algorithm::Rotate),
14/// and [`Skew`](crate::algorithm::Skew).
15///
16/// # Examples
17/// ## Build up transforms by beginning with a constructor, then chaining mutation operations
18/// ```
19/// use geo::{AffineOps, AffineTransform};
20/// use geo::{line_string, BoundingRect, Point, LineString};
21/// use approx::assert_relative_eq;
22///
23/// let ls: LineString = line_string![
24/// (x: 0.0f64, y: 0.0f64),
25/// (x: 0.0f64, y: 10.0f64),
26/// ];
27/// let center = ls.bounding_rect().unwrap().center();
28///
29/// let transform = AffineTransform::skew(40.0, 40.0, center).rotated(45.0, center);
30///
31/// let skewed_rotated = ls.affine_transform(&transform);
32///
33/// assert_relative_eq!(skewed_rotated, line_string![
34/// (x: 0.5688687f64, y: 4.4311312),
35/// (x: -0.5688687, y: 5.5688687)
36/// ], max_relative = 1.0);
37/// ```
38pub trait AffineOps<T: CoordNum> {
39 /// Apply `transform` immutably, outputting a new geometry.
40 #[must_use]
41 fn affine_transform(&self, transform: &AffineTransform<T>) -> Self;
42
43 /// Apply `transform` to mutate `self`.
44 fn affine_transform_mut(&mut self, transform: &AffineTransform<T>);
45}
46
47impl<T: CoordNum, M: MapCoordsInPlace<T> + MapCoords<T, T, Output = Self>> AffineOps<T> for M {
48 fn affine_transform(&self, transform: &AffineTransform<T>) -> Self {
49 self.map_coords(|c| transform.apply(c))
50 }
51
52 fn affine_transform_mut(&mut self, transform: &AffineTransform<T>) {
53 self.map_coords_in_place(|c| transform.apply(c))
54 }
55}
56
57/// A general affine transformation matrix, and associated operations.
58///
59/// Note that affine ops are **already implemented** on most `geo-types` primitives, using this module.
60///
61/// Affine transforms using the same numeric type (e.g. [`CoordFloat`](crate::CoordFloat)) can be **composed**,
62/// and the result can be applied to geometries using e.g. [`MapCoords`](crate::MapCoords). This allows the
63/// efficient application of transforms: an arbitrary number of operations can be chained.
64/// These are then composed, producing a final transformation matrix which is applied to the geometry coordinates.
65///
66/// `AffineTransform` is a row-major matrix.
67/// 2D affine transforms require six matrix parameters:
68///
69/// `[a, b, xoff, d, e, yoff]`
70///
71/// these map onto the `AffineTransform` rows as follows:
72/// ```ignore
73/// [[a, b, xoff],
74/// [d, e, yoff],
75/// [0, 0, 1]]
76/// ```
77/// The equations for transforming coordinates `(x, y) -> (x', y')` are given as follows:
78///
79/// `x' = ax + by + xoff`
80///
81/// `y' = dx + ey + yoff`
82///
83/// # Usage
84///
85/// Two types of operation are provided: construction and mutation. **Construction** functions create a *new* transform
86/// and are denoted by the use of the **present tense**: `scale()`, `translate()`, `rotate()`, and `skew()`.
87///
88/// **Mutation** methods *add* a transform to the existing `AffineTransform`, and are denoted by the use of the past participle:
89/// `scaled()`, `translated()`, `rotated()`, and `skewed()`.
90///
91/// # Examples
92/// ## Build up transforms by beginning with a constructor, then chaining mutation operations
93/// ```
94/// use geo::{AffineOps, AffineTransform};
95/// use geo::{line_string, BoundingRect, Point, LineString};
96/// use approx::assert_relative_eq;
97///
98/// let ls: LineString = line_string![
99/// (x: 0.0f64, y: 0.0f64),
100/// (x: 0.0f64, y: 10.0f64),
101/// ];
102/// let center = ls.bounding_rect().unwrap().center();
103///
104/// let transform = AffineTransform::skew(40.0, 40.0, center).rotated(45.0, center);
105///
106/// let skewed_rotated = ls.affine_transform(&transform);
107///
108/// assert_relative_eq!(skewed_rotated, line_string![
109/// (x: 0.5688687f64, y: 4.4311312),
110/// (x: -0.5688687, y: 5.5688687)
111/// ], max_relative = 1.0);
112/// ```
113#[derive(Copy, Clone, PartialEq, Eq)]
114pub struct AffineTransform<T: CoordNum = f64>([[T; 3]; 3]);
115
116impl<T: CoordNum> Default for AffineTransform<T> {
117 fn default() -> Self {
118 // identity matrix
119 Self::identity()
120 }
121}
122
123impl<T: CoordNum> AffineTransform<T> {
124 /// Create a new affine transformation by composing two `AffineTransform`s.
125 ///
126 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
127 #[must_use]
128 pub fn compose(&self, other: &Self) -> Self {
129 // lol
130 Self([
131 [
132 (self.0[0][0] * other.0[0][0])
133 + (self.0[0][1] * other.0[1][0])
134 + (self.0[0][2] * other.0[2][0]),
135 (self.0[0][0] * other.0[0][1])
136 + (self.0[0][1] * other.0[1][1])
137 + (self.0[0][2] * other.0[2][1]),
138 (self.0[0][0] * other.0[0][2])
139 + (self.0[0][1] * other.0[1][2])
140 + (self.0[0][2] * other.0[2][2]),
141 ],
142 [
143 (self.0[1][0] * other.0[0][0])
144 + (self.0[1][1] * other.0[1][0])
145 + (self.0[1][2] * other.0[2][0]),
146 (self.0[1][0] * other.0[0][1])
147 + (self.0[1][1] * other.0[1][1])
148 + (self.0[1][2] * other.0[2][1]),
149 (self.0[1][0] * other.0[0][2])
150 + (self.0[1][1] * other.0[1][2])
151 + (self.0[1][2] * other.0[2][2]),
152 ],
153 [
154 // this section isn't technically necessary since the last row is invariant: [0, 0, 1]
155 (self.0[2][0] * other.0[0][0])
156 + (self.0[2][1] * other.0[1][0])
157 + (self.0[2][2] * other.0[2][0]),
158 (self.0[2][0] * other.0[0][1])
159 + (self.0[2][1] * other.0[1][1])
160 + (self.0[2][2] * other.0[2][1]),
161 (self.0[2][0] * other.0[0][2])
162 + (self.0[2][1] * other.0[1][2])
163 + (self.0[2][2] * other.0[2][2]),
164 ],
165 ])
166 }
167 /// Create the identity matrix
168 ///
169 /// The matrix is:
170 /// ```ignore
171 /// [[1, 0, 0],
172 /// [0, 1, 0],
173 /// [0, 0, 1]]
174 /// ```
175 pub fn identity() -> Self {
176 Self::new(
177 T::one(),
178 T::zero(),
179 T::zero(),
180 T::zero(),
181 T::one(),
182 T::zero(),
183 )
184 }
185
186 /// Whether the transformation is equivalent to the [identity matrix](Self::identity),
187 /// that is, whether it's application will be a a no-op.
188 ///
189 /// ```
190 /// use geo::AffineTransform;
191 /// let mut transform = AffineTransform::identity();
192 /// assert!(transform.is_identity());
193 ///
194 /// // mutate the transform a bit
195 /// transform = transform.translated(1.0, 2.0);
196 /// assert!(!transform.is_identity());
197 ///
198 /// // put it back
199 /// transform = transform.translated(-1.0, -2.0);
200 /// assert!(transform.is_identity());
201 /// ```
202 pub fn is_identity(&self) -> bool {
203 self == &Self::identity()
204 }
205
206 /// **Create** a new affine transform for scaling, scaled by factors along the `x` and `y` dimensions.
207 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
208 /// any coordinate may be specified.
209 /// Negative scale factors will mirror or reflect coordinates.
210 ///
211 /// The matrix is:
212 /// ```ignore
213 /// [[xfact, 0, xoff],
214 /// [0, yfact, yoff],
215 /// [0, 0, 1]]
216 ///
217 /// xoff = origin.x - (origin.x * xfact)
218 /// yoff = origin.y - (origin.y * yfact)
219 /// ```
220 pub fn scale(xfact: T, yfact: T, origin: impl Into<Coord<T>>) -> Self {
221 let (x0, y0) = origin.into().x_y();
222 let xoff = x0 - (x0 * xfact);
223 let yoff = y0 - (y0 * yfact);
224 Self::new(xfact, T::zero(), xoff, T::zero(), yfact, yoff)
225 }
226
227 /// **Add** an affine transform for scaling, scaled by factors along the `x` and `y` dimensions.
228 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
229 /// any coordinate may be specified.
230 /// Negative scale factors will mirror or reflect coordinates.
231 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
232 #[must_use]
233 pub fn scaled(mut self, xfact: T, yfact: T, origin: impl Into<Coord<T>>) -> Self {
234 self.0 = self.compose(&Self::scale(xfact, yfact, origin)).0;
235 self
236 }
237
238 /// **Create** an affine transform for translation, shifted by offsets along the `x` and `y` dimensions.
239 ///
240 /// The matrix is:
241 /// ```ignore
242 /// [[1, 0, xoff],
243 /// [0, 1, yoff],
244 /// [0, 0, 1]]
245 /// ```
246 pub fn translate(xoff: T, yoff: T) -> Self {
247 Self::new(T::one(), T::zero(), xoff, T::zero(), T::one(), yoff)
248 }
249
250 /// **Add** an affine transform for translation, shifted by offsets along the `x` and `y` dimensions
251 ///
252 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
253 #[must_use]
254 pub fn translated(mut self, xoff: T, yoff: T) -> Self {
255 self.0 = self.compose(&Self::translate(xoff, yoff)).0;
256 self
257 }
258
259 /// Apply the current transform to a coordinate
260 pub fn apply(&self, coord: Coord<T>) -> Coord<T> {
261 Coord {
262 x: (self.0[0][0] * coord.x + self.0[0][1] * coord.y + self.0[0][2]),
263 y: (self.0[1][0] * coord.x + self.0[1][1] * coord.y + self.0[1][2]),
264 }
265 }
266
267 /// Create a new custom transform matrix
268 ///
269 /// The argument order matches that of the affine transform matrix:
270 ///```ignore
271 /// [[a, b, xoff],
272 /// [d, e, yoff],
273 /// [0, 0, 1]] <-- not part of the input arguments
274 /// ```
275 pub fn new(a: T, b: T, xoff: T, d: T, e: T, yoff: T) -> Self {
276 Self([[a, b, xoff], [d, e, yoff], [T::zero(), T::zero(), T::one()]])
277 }
278}
279
280impl<T: CoordNum> fmt::Debug for AffineTransform<T> {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 f.debug_struct("AffineTransform")
283 .field("a", &self.0[0][0])
284 .field("b", &self.0[0][1])
285 .field("xoff", &self.0[1][2])
286 .field("d", &self.0[1][0])
287 .field("e", &self.0[1][1])
288 .field("yoff", &self.0[1][2])
289 .finish()
290 }
291}
292
293impl<T: CoordNum> From<[T; 6]> for AffineTransform<T> {
294 fn from(arr: [T; 6]) -> Self {
295 Self::new(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5])
296 }
297}
298
299impl<T: CoordNum> From<(T, T, T, T, T, T)> for AffineTransform<T> {
300 fn from(tup: (T, T, T, T, T, T)) -> Self {
301 Self::new(tup.0, tup.1, tup.2, tup.3, tup.4, tup.5)
302 }
303}
304
305impl<U: CoordFloat> AffineTransform<U> {
306 /// **Create** an affine transform for rotation, using an arbitrary point as its centre.
307 ///
308 /// Note that this operation is only available for geometries with floating point coordinates.
309 ///
310 /// `angle` is given in **degrees**.
311 ///
312 /// The matrix (angle denoted as theta) is:
313 /// ```ignore
314 /// [[cos_theta, -sin_theta, xoff],
315 /// [sin_theta, cos_theta, yoff],
316 /// [0, 0, 1]]
317 ///
318 /// xoff = origin.x - (origin.x * cos(theta)) + (origin.y * sin(theta))
319 /// yoff = origin.y - (origin.x * sin(theta)) + (origin.y * cos(theta))
320 /// ```
321 pub fn rotate(degrees: U, origin: impl Into<Coord<U>>) -> Self {
322 let (sin_theta, cos_theta) = degrees.to_radians().sin_cos();
323 let (x0, y0) = origin.into().x_y();
324 let xoff = x0 - (x0 * cos_theta) + (y0 * sin_theta);
325 let yoff = y0 - (x0 * sin_theta) - (y0 * cos_theta);
326 Self::new(cos_theta, -sin_theta, xoff, sin_theta, cos_theta, yoff)
327 }
328
329 /// **Add** an affine transform for rotation, using an arbitrary point as its centre.
330 ///
331 /// Note that this operation is only available for geometries with floating point coordinates.
332 ///
333 /// `angle` is given in **degrees**.
334 ///
335 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
336 #[must_use]
337 pub fn rotated(mut self, angle: U, origin: impl Into<Coord<U>>) -> Self {
338 self.0 = self.compose(&Self::rotate(angle, origin)).0;
339 self
340 }
341
342 /// **Create** an affine transform for skewing.
343 ///
344 /// Note that this operation is only available for geometries with floating point coordinates.
345 ///
346 /// Geometries are sheared by angles along x (`xs`) and y (`ys`) dimensions.
347 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
348 /// any coordinate may be specified. Angles are given in **degrees**.
349 /// The matrix is:
350 /// ```ignore
351 /// [[1, tan(x), xoff],
352 /// [tan(y), 1, yoff],
353 /// [0, 0, 1]]
354 ///
355 /// xoff = -origin.y * tan(xs)
356 /// yoff = -origin.x * tan(ys)
357 /// ```
358 pub fn skew(xs: U, ys: U, origin: impl Into<Coord<U>>) -> Self {
359 let Coord { x: x0, y: y0 } = origin.into();
360 let mut tanx = xs.to_radians().tan();
361 let mut tany = ys.to_radians().tan();
362 // These checks are stolen from Shapely's implementation -- may not be necessary
363 if tanx.abs() < U::from::<f64>(2.5e-16).unwrap() {
364 tanx = U::zero();
365 }
366 if tany.abs() < U::from::<f64>(2.5e-16).unwrap() {
367 tany = U::zero();
368 }
369 let xoff = -y0 * tanx;
370 let yoff = -x0 * tany;
371 Self::new(U::one(), tanx, xoff, tany, U::one(), yoff)
372 }
373
374 /// **Add** an affine transform for skewing.
375 ///
376 /// Note that this operation is only available for geometries with floating point coordinates.
377 ///
378 /// Geometries are sheared by angles along x (`xs`) and y (`ys`) dimensions.
379 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
380 /// any coordinate may be specified. Angles are given in **degrees**.
381 ///
382 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
383 #[must_use]
384 pub fn skewed(mut self, xs: U, ys: U, origin: impl Into<Coord<U>>) -> Self {
385 self.0 = self.compose(&Self::skew(xs, ys, origin)).0;
386 self
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::{polygon, Point};
394
395 // given a matrix with the shape
396 // [[a, b, xoff],
397 // [d, e, yoff],
398 // [0, 0, 1]]
399 #[test]
400 fn matrix_multiply() {
401 let a = AffineTransform::new(1, 2, 5, 3, 4, 6);
402 let b = AffineTransform::new(7, 8, 11, 9, 10, 12);
403 let composed = a.compose(&b);
404 assert_eq!(composed.0[0][0], 25);
405 assert_eq!(composed.0[0][1], 28);
406 assert_eq!(composed.0[0][2], 40);
407 assert_eq!(composed.0[1][0], 57);
408 assert_eq!(composed.0[1][1], 64);
409 assert_eq!(composed.0[1][2], 87);
410 }
411 #[test]
412 fn test_transform_composition() {
413 let p0 = Point::new(0.0f64, 0.0);
414 // scale once
415 let mut scale_a = AffineTransform::default().scaled(2.0, 2.0, p0);
416 // rotate
417 scale_a = scale_a.rotated(45.0, p0);
418 // rotate back
419 scale_a = scale_a.rotated(-45.0, p0);
420 // scale up again, doubling
421 scale_a = scale_a.scaled(2.0, 2.0, p0);
422 // scaled once
423 let scale_b = AffineTransform::default().scaled(2.0, 2.0, p0);
424 // scaled once, but equal to 2 + 2
425 let scale_c = AffineTransform::default().scaled(4.0, 4.0, p0);
426 assert_ne!(&scale_a.0, &scale_b.0);
427 assert_eq!(&scale_a.0, &scale_c.0);
428 }
429
430 #[test]
431 fn affine_transformed() {
432 let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, (0.0, 0.0));
433 let mut poly = polygon![(x: 0.0, y: 0.0), (x: 0.0, y: 2.0), (x: 1.0, y: 2.0)];
434 poly.affine_transform_mut(&transform);
435
436 let expected = polygon![(x: 1.0, y: 1.0), (x: 1.0, y: 5.0), (x: 3.0, y: 5.0)];
437 assert_eq!(expected, poly);
438 }
439}