geo/algorithm/affine_ops.rs
1use num_traits::ToPrimitive;
2
3use crate::{Coord, CoordFloat, CoordNum, MapCoords, MapCoordsInPlace};
4use std::{fmt, ops::Mul, ops::Neg};
5
6/// Apply an [`AffineTransform`] like [`scale`](AffineTransform::scale),
7/// [`skew`](AffineTransform::skew), or [`rotate`](AffineTransform::rotate) to a
8/// [`Geometry`](crate::geometry::Geometry).
9///
10/// Multiple transformations can be composed in order to be efficiently applied in a single
11/// operation. See [`AffineTransform`] for more on how to build up a transformation.
12///
13/// If you are not composing operations, traits that leverage this same machinery exist which might
14/// be more readable. See: [`Scale`](crate::algorithm::Scale),
15/// [`Translate`](crate::algorithm::Translate), [`Rotate`](crate::algorithm::Rotate),
16/// and [`Skew`](crate::algorithm::Skew).
17///
18/// # Examples
19/// ## Build up transforms by beginning with a constructor, then chaining mutation operations
20/// ```
21/// use geo::{AffineOps, AffineTransform};
22/// use geo::{point, line_string, BoundingRect};
23/// use approx::assert_relative_eq;
24///
25/// let line_string = line_string![(x: 0.0, y: 0.0),(x: 1.0, y: 1.0)];
26///
27/// let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, point!(x: 0.0, y: 0.0));
28///
29/// let transformed_line_string = line_string.affine_transform(&transform);
30///
31/// assert_relative_eq!(
32/// transformed_line_string,
33/// line_string![(x: 2.0, y: 2.0),(x: 4.0, y: 4.0)]
34/// );
35/// ```
36pub trait AffineOps<T: CoordNum> {
37 /// Apply `transform` immutably, outputting a new geometry.
38 #[must_use]
39 fn affine_transform(&self, transform: &AffineTransform<T>) -> Self;
40
41 /// Apply `transform` to mutate `self`.
42 fn affine_transform_mut(&mut self, transform: &AffineTransform<T>);
43}
44
45impl<T: CoordNum, M: MapCoordsInPlace<T> + MapCoords<T, T, Output = Self>> AffineOps<T> for M {
46 fn affine_transform(&self, transform: &AffineTransform<T>) -> Self {
47 self.map_coords(|c| transform.apply(c))
48 }
49
50 fn affine_transform_mut(&mut self, transform: &AffineTransform<T>) {
51 self.map_coords_in_place(|c| transform.apply(c))
52 }
53}
54
55/// A general affine transformation matrix, and associated operations.
56///
57/// Note that affine ops are **already implemented** on most `geo-types` primitives, using this module.
58///
59/// Affine transforms using the same numeric type (e.g. [`CoordFloat`]) can be **composed**,
60/// and the result can be applied to geometries using e.g. [`MapCoords`]. This allows the
61/// efficient application of transforms: an arbitrary number of operations can be chained.
62/// These are then composed, producing a final transformation matrix which is applied to the geometry coordinates.
63///
64/// `AffineTransform` is a row-major matrix.
65/// 2D affine transforms require six matrix parameters:
66///
67/// `[a, b, xoff, d, e, yoff]`
68///
69/// these map onto the `AffineTransform` rows as follows:
70/// ```ignore
71/// [[a, b, xoff],
72/// [d, e, yoff],
73/// [0, 0, 1]]
74/// ```
75/// The equations for transforming coordinates `(x, y) -> (x', y')` are given as follows:
76///
77/// `x' = ax + by + xoff`
78///
79/// `y' = dx + ey + yoff`
80///
81/// # Usage
82///
83/// Two types of operation are provided: construction and mutation. **Construction** functions create a *new* transform
84/// and are denoted by the use of the **present tense**: `scale()`, `translate()`, `rotate()`, and `skew()`.
85///
86/// **Mutation** methods *add* a transform to the existing `AffineTransform`, and are denoted by the use of the past participle:
87/// `scaled()`, `translated()`, `rotated()`, and `skewed()`.
88///
89/// # Examples
90/// ## Build up transforms by beginning with a constructor, then chaining mutation operations
91/// ```
92/// use geo::{AffineOps, AffineTransform};
93/// use geo::{point, line_string, BoundingRect};
94/// use approx::assert_relative_eq;
95///
96/// let line_string = line_string![(x: 0.0, y: 0.0),(x: 1.0, y: 1.0)];
97///
98/// let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, point!(x: 0.0, y: 0.0));
99///
100/// let transformed_line_string = line_string.affine_transform(&transform);
101///
102/// assert_relative_eq!(
103/// transformed_line_string,
104/// line_string![(x: 2.0, y: 2.0),(x: 4.0, y: 4.0)]
105/// );
106/// ```
107///
108/// ## Create affine transform manually, and access elements using getter methods
109/// ```
110/// use geo::AffineTransform;
111///
112/// let transform = AffineTransform::new(10.0, 0.0, 400_000.0, 0.0, -10.0, 500_000.0);
113///
114/// let a: f64 = transform.a();
115/// let b: f64 = transform.b();
116/// let xoff: f64 = transform.xoff();
117/// let d: f64 = transform.d();
118/// let e: f64 = transform.e();
119/// let yoff: f64 = transform.yoff();
120/// assert_eq!(transform, AffineTransform::new(a, b, xoff, d, e, yoff))
121/// ```
122
123#[derive(Copy, Clone, PartialEq, Eq)]
124pub struct AffineTransform<T: CoordNum = f64>([[T; 3]; 3]);
125
126impl<T: CoordNum> Default for AffineTransform<T> {
127 fn default() -> Self {
128 // identity matrix
129 Self::identity()
130 }
131}
132
133impl<T: CoordNum> AffineTransform<T> {
134 /// Create a new affine transformation by composing two `AffineTransform`s.
135 ///
136 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
137 #[must_use]
138 pub fn compose(&self, other: &Self) -> Self {
139 // lol
140 Self([
141 [
142 (other.0[0][0] * self.0[0][0])
143 + (other.0[0][1] * self.0[1][0])
144 + (other.0[0][2] * self.0[2][0]),
145 (other.0[0][0] * self.0[0][1])
146 + (other.0[0][1] * self.0[1][1])
147 + (other.0[0][2] * self.0[2][1]),
148 (other.0[0][0] * self.0[0][2])
149 + (other.0[0][1] * self.0[1][2])
150 + (other.0[0][2] * self.0[2][2]),
151 ],
152 [
153 (other.0[1][0] * self.0[0][0])
154 + (other.0[1][1] * self.0[1][0])
155 + (other.0[1][2] * self.0[2][0]),
156 (other.0[1][0] * self.0[0][1])
157 + (other.0[1][1] * self.0[1][1])
158 + (other.0[1][2] * self.0[2][1]),
159 (other.0[1][0] * self.0[0][2])
160 + (other.0[1][1] * self.0[1][2])
161 + (other.0[1][2] * self.0[2][2]),
162 ],
163 [
164 // this section isn't technically necessary since the last row is invariant: [0, 0, 1]
165 (other.0[2][0] * self.0[0][0])
166 + (other.0[2][1] * self.0[1][0])
167 + (other.0[2][2] * self.0[2][0]),
168 (other.0[2][0] * self.0[0][1])
169 + (other.0[2][1] * self.0[1][1])
170 + (other.0[2][2] * self.0[2][1]),
171 (other.0[2][0] * self.0[0][2])
172 + (other.0[2][1] * self.0[1][2])
173 + (other.0[2][2] * self.0[2][2]),
174 ],
175 ])
176 }
177
178 /// Create a new affine transformation by composing an arbitrary number of `AffineTransform`s.
179 ///
180 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
181 /// ```
182 /// use geo::AffineTransform;
183 /// let mut transform = AffineTransform::identity();
184 ///
185 /// // create two transforms that cancel each other
186 /// let transform1 = AffineTransform::translate(1.0, 2.0);
187 /// let transform2 = AffineTransform::translate(-1.0, -2.0);
188 /// let transforms = vec![transform1, transform2];
189 ///
190 /// // apply them
191 /// let outcome = transform.compose_many(&transforms);
192 /// // we should be back to square one
193 /// assert!(outcome.is_identity());
194 /// ```
195 #[must_use]
196 pub fn compose_many(&self, transforms: &[Self]) -> Self {
197 self.compose(&transforms.iter().fold(
198 AffineTransform::default(),
199 |acc: AffineTransform<T>, transform| acc.compose(transform),
200 ))
201 }
202
203 /// Create the identity matrix
204 ///
205 /// The matrix is:
206 /// ```ignore
207 /// [[1, 0, 0],
208 /// [0, 1, 0],
209 /// [0, 0, 1]]
210 /// ```
211 pub fn identity() -> Self {
212 Self::new(
213 T::one(),
214 T::zero(),
215 T::zero(),
216 T::zero(),
217 T::one(),
218 T::zero(),
219 )
220 }
221
222 /// Whether the transformation is equivalent to the [identity matrix](Self::identity),
223 /// that is, whether it's application will be a a no-op.
224 ///
225 /// ```
226 /// use geo::AffineTransform;
227 /// let mut transform = AffineTransform::identity();
228 /// assert!(transform.is_identity());
229 ///
230 /// // mutate the transform a bit
231 /// transform = transform.translated(1.0, 2.0);
232 /// assert!(!transform.is_identity());
233 ///
234 /// // put it back
235 /// transform = transform.translated(-1.0, -2.0);
236 /// assert!(transform.is_identity());
237 /// ```
238 pub fn is_identity(&self) -> bool {
239 self == &Self::identity()
240 }
241
242 /// **Create** a new affine transform for scaling, scaled by factors along the `x` and `y` dimensions.
243 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
244 /// any coordinate may be specified.
245 /// Negative scale factors will mirror or reflect coordinates.
246 ///
247 /// The matrix is:
248 /// ```ignore
249 /// [[xfact, 0, xoff],
250 /// [0, yfact, yoff],
251 /// [0, 0, 1]]
252 ///
253 /// xoff = origin.x - (origin.x * xfact)
254 /// yoff = origin.y - (origin.y * yfact)
255 /// ```
256 pub fn scale(xfact: T, yfact: T, origin: impl Into<Coord<T>>) -> Self {
257 let (x0, y0) = origin.into().x_y();
258 let xoff = x0 - (x0 * xfact);
259 let yoff = y0 - (y0 * yfact);
260 Self::new(xfact, T::zero(), xoff, T::zero(), yfact, yoff)
261 }
262
263 /// **Add** an affine transform for scaling, scaled by factors along the `x` and `y` dimensions.
264 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
265 /// any coordinate may be specified.
266 /// Negative scale factors will mirror or reflect coordinates.
267 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
268 #[must_use]
269 pub fn scaled(mut self, xfact: T, yfact: T, origin: impl Into<Coord<T>>) -> Self {
270 self.0 = self.compose(&Self::scale(xfact, yfact, origin)).0;
271 self
272 }
273
274 /// **Create** an affine transform for translation, shifted by offsets along the `x` and `y` dimensions.
275 ///
276 /// The matrix is:
277 /// ```ignore
278 /// [[1, 0, xoff],
279 /// [0, 1, yoff],
280 /// [0, 0, 1]]
281 /// ```
282 pub fn translate(xoff: T, yoff: T) -> Self {
283 Self::new(T::one(), T::zero(), xoff, T::zero(), T::one(), yoff)
284 }
285
286 /// **Add** an affine transform for translation, shifted by offsets along the `x` and `y` dimensions
287 ///
288 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
289 #[must_use]
290 pub fn translated(mut self, xoff: T, yoff: T) -> Self {
291 self.0 = self.compose(&Self::translate(xoff, yoff)).0;
292 self
293 }
294
295 /// Apply the current transform to a coordinate
296 pub fn apply(&self, coord: Coord<T>) -> Coord<T> {
297 Coord {
298 x: (self.0[0][0] * coord.x + self.0[0][1] * coord.y + self.0[0][2]),
299 y: (self.0[1][0] * coord.x + self.0[1][1] * coord.y + self.0[1][2]),
300 }
301 }
302
303 /// Create a new custom transform matrix
304 ///
305 /// The argument order matches that of the affine transform matrix:
306 ///```ignore
307 /// [[a, b, xoff],
308 /// [d, e, yoff],
309 /// [0, 0, 1]] <-- not part of the input arguments
310 /// ```
311 pub fn new(a: T, b: T, xoff: T, d: T, e: T, yoff: T) -> Self {
312 Self([[a, b, xoff], [d, e, yoff], [T::zero(), T::zero(), T::one()]])
313 }
314
315 /// See [AffineTransform::new] for this value's role in the affine transformation.
316 pub fn a(&self) -> T {
317 self.0[0][0]
318 }
319 /// See [AffineTransform::new] for this value's role in the affine transformation.
320 pub fn b(&self) -> T {
321 self.0[0][1]
322 }
323 /// See [AffineTransform::new] for this value's role in the affine transformation.
324 pub fn xoff(&self) -> T {
325 self.0[0][2]
326 }
327 /// See [AffineTransform::new] for this value's role in the affine transformation.
328 pub fn d(&self) -> T {
329 self.0[1][0]
330 }
331 /// See [AffineTransform::new] for this value's role in the affine transformation.
332 pub fn e(&self) -> T {
333 self.0[1][1]
334 }
335 /// See [AffineTransform::new] for this value's role in the affine transformation.
336 pub fn yoff(&self) -> T {
337 self.0[1][2]
338 }
339}
340
341impl<T: CoordNum + Neg> AffineTransform<T> {
342 /// Return the inverse of a given transform. Composing a transform with its inverse yields
343 /// the [identity matrix](Self::identity)
344 #[must_use]
345 pub fn inverse(&self) -> Option<Self>
346 where
347 <T as Neg>::Output: Mul<T>,
348 <<T as Neg>::Output as Mul<T>>::Output: ToPrimitive,
349 {
350 let a = self.0[0][0];
351 let b = self.0[0][1];
352 let xoff = self.0[0][2];
353 let d = self.0[1][0];
354 let e = self.0[1][1];
355 let yoff = self.0[1][2];
356
357 let determinant = a * e - b * d;
358
359 if determinant == T::zero() {
360 return None; // The matrix is not invertible
361 }
362 let inv_det = T::one() / determinant;
363
364 // If conversion of either the b or d matrix value fails, bail out
365 Some(Self::new(
366 e * inv_det,
367 T::from(-b * inv_det)?,
368 (b * yoff - e * xoff) * inv_det,
369 T::from(-d * inv_det)?,
370 a * inv_det,
371 (d * xoff - a * yoff) * inv_det,
372 ))
373 }
374}
375
376impl<T: CoordNum> fmt::Debug for AffineTransform<T> {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 f.debug_struct("AffineTransform")
379 .field("a", &self.0[0][0])
380 .field("b", &self.0[0][1])
381 .field("xoff", &self.0[0][2])
382 .field("d", &self.0[1][0])
383 .field("e", &self.0[1][1])
384 .field("yoff", &self.0[1][2])
385 .finish()
386 }
387}
388
389impl<T: CoordNum> From<[T; 6]> for AffineTransform<T> {
390 fn from(arr: [T; 6]) -> Self {
391 Self::new(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5])
392 }
393}
394
395impl<T: CoordNum> From<(T, T, T, T, T, T)> for AffineTransform<T> {
396 fn from(tup: (T, T, T, T, T, T)) -> Self {
397 Self::new(tup.0, tup.1, tup.2, tup.3, tup.4, tup.5)
398 }
399}
400
401impl<U: CoordFloat> AffineTransform<U> {
402 /// **Create** an affine transform for rotation, using an arbitrary point as its centre.
403 ///
404 /// Note that this operation is only available for geometries with floating point coordinates.
405 ///
406 /// `angle` is given in **degrees**.
407 ///
408 /// The matrix (angle denoted as theta) is:
409 /// ```ignore
410 /// [[cos_theta, -sin_theta, xoff],
411 /// [sin_theta, cos_theta, yoff],
412 /// [0, 0, 1]]
413 ///
414 /// xoff = origin.x - (origin.x * cos(theta)) + (origin.y * sin(theta))
415 /// yoff = origin.y - (origin.x * sin(theta)) + (origin.y * cos(theta))
416 /// ```
417 pub fn rotate(degrees: U, origin: impl Into<Coord<U>>) -> Self {
418 let (sin_theta, cos_theta) = degrees.to_radians().sin_cos();
419 let (x0, y0) = origin.into().x_y();
420 let xoff = x0 - (x0 * cos_theta) + (y0 * sin_theta);
421 let yoff = y0 - (x0 * sin_theta) - (y0 * cos_theta);
422 Self::new(cos_theta, -sin_theta, xoff, sin_theta, cos_theta, yoff)
423 }
424
425 /// **Add** an affine transform for rotation, using an arbitrary point as its centre.
426 ///
427 /// Note that this operation is only available for geometries with floating point coordinates.
428 ///
429 /// `angle` is given in **degrees**.
430 ///
431 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
432 #[must_use]
433 pub fn rotated(mut self, angle: U, origin: impl Into<Coord<U>>) -> Self {
434 self.0 = self.compose(&Self::rotate(angle, origin)).0;
435 self
436 }
437
438 /// **Create** an affine transform for skewing.
439 ///
440 /// Note that this operation is only available for geometries with floating point coordinates.
441 ///
442 /// Geometries are sheared by angles along x (`xs`) and y (`ys`) dimensions.
443 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
444 /// any coordinate may be specified. Angles are given in **degrees**.
445 /// The matrix is:
446 /// ```ignore
447 /// [[1, tan(x), xoff],
448 /// [tan(y), 1, yoff],
449 /// [0, 0, 1]]
450 ///
451 /// xoff = -origin.y * tan(xs)
452 /// yoff = -origin.x * tan(ys)
453 /// ```
454 pub fn skew(xs: U, ys: U, origin: impl Into<Coord<U>>) -> Self {
455 let Coord { x: x0, y: y0 } = origin.into();
456 let mut tanx = xs.to_radians().tan();
457 let mut tany = ys.to_radians().tan();
458 // These checks are stolen from Shapely's implementation -- may not be necessary
459 if tanx.abs() < U::from::<f64>(2.5e-16).unwrap() {
460 tanx = U::zero();
461 }
462 if tany.abs() < U::from::<f64>(2.5e-16).unwrap() {
463 tany = U::zero();
464 }
465 let xoff = -y0 * tanx;
466 let yoff = -x0 * tany;
467 Self::new(U::one(), tanx, xoff, tany, U::one(), yoff)
468 }
469
470 /// **Add** an affine transform for skewing.
471 ///
472 /// Note that this operation is only available for geometries with floating point coordinates.
473 ///
474 /// Geometries are sheared by angles along x (`xs`) and y (`ys`) dimensions.
475 /// The point of origin is *usually* given as the 2D bounding box centre of the geometry, but
476 /// any coordinate may be specified. Angles are given in **degrees**.
477 ///
478 /// This is a **cumulative** operation; the new transform is *added* to the existing transform.
479 #[must_use]
480 pub fn skewed(mut self, xs: U, ys: U, origin: impl Into<Coord<U>>) -> Self {
481 self.0 = self.compose(&Self::skew(xs, ys, origin)).0;
482 self
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use approx::{AbsDiffEq, RelativeEq};
489
490 impl<T> RelativeEq for AffineTransform<T>
491 where
492 T: AbsDiffEq<Epsilon = T> + CoordNum + RelativeEq,
493 {
494 #[inline]
495 fn default_max_relative() -> Self::Epsilon {
496 T::default_max_relative()
497 }
498
499 /// Equality assertion within a relative limit.
500 ///
501 /// # Examples
502 ///
503 /// ```
504 /// use geo_types::AffineTransform;
505 /// use geo_types::point;
506 ///
507 /// let a = AffineTransform::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
508 /// let b = AffineTransform::new(1.01, 2.02, 3.03, 4.04, 5.05, 6.06);
509 ///
510 /// approx::assert_relative_eq!(a, b, max_relative=0.1)
511 /// approx::assert_relative_ne!(a, b, max_relative=0.055)
512 /// ```
513 #[inline]
514 fn relative_eq(
515 &self,
516 other: &Self,
517 epsilon: Self::Epsilon,
518 max_relative: Self::Epsilon,
519 ) -> bool {
520 let mut mp_zipper = self.0.iter().flatten().zip(other.0.iter().flatten());
521 mp_zipper.all(|(lhs, rhs)| lhs.relative_eq(rhs, epsilon, max_relative))
522 }
523 }
524
525 impl<T> AbsDiffEq for AffineTransform<T>
526 where
527 T: AbsDiffEq<Epsilon = T> + CoordNum,
528 T::Epsilon: Copy,
529 {
530 type Epsilon = T;
531
532 #[inline]
533 fn default_epsilon() -> Self::Epsilon {
534 T::default_epsilon()
535 }
536
537 /// Equality assertion with an absolute limit.
538 ///
539 /// # Examples
540 ///
541 /// ```
542 /// use geo_types::MultiPoint;
543 /// use geo_types::point;
544 ///
545 /// let a = AffineTransform::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
546 /// let b = AffineTransform::new(1.01, 2.02, 3.03, 4.04, 5.05, 6.06);
547 ///
548 /// approx::abs_diff_eq!(a, b, epsilon=0.1)
549 /// approx::abs_diff_ne!(a, b, epsilon=0.055)
550 /// ```
551 #[inline]
552 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
553 let mut mp_zipper = self.0.iter().flatten().zip(other.0.iter().flatten());
554 mp_zipper.all(|(lhs, rhs)| lhs.abs_diff_eq(rhs, epsilon))
555 }
556 }
557
558 use super::*;
559 use crate::{wkt, Point};
560
561 // given a matrix with the shape
562 // [[a, b, xoff],
563 // [d, e, yoff],
564 // [0, 0, 1]]
565 #[test]
566 fn matrix_multiply() {
567 let a = AffineTransform::new(1, 2, 5, 3, 4, 6);
568 let b = AffineTransform::new(7, 8, 11, 9, 10, 12);
569 let composed = a.compose(&b);
570 assert_eq!(composed.0[0][0], 31);
571 assert_eq!(composed.0[0][1], 46);
572 assert_eq!(composed.0[0][2], 94);
573 assert_eq!(composed.0[1][0], 39);
574 assert_eq!(composed.0[1][1], 58);
575 assert_eq!(composed.0[1][2], 117);
576 }
577 #[test]
578 fn test_transform_composition() {
579 let p0 = Point::new(0.0f64, 0.0);
580 // scale once
581 let mut scale_a = AffineTransform::default().scaled(2.0, 2.0, p0);
582 // rotate
583 scale_a = scale_a.rotated(45.0, p0);
584 // rotate back
585 scale_a = scale_a.rotated(-45.0, p0);
586 // scale up again, doubling
587 scale_a = scale_a.scaled(2.0, 2.0, p0);
588 // scaled once
589 let scale_b = AffineTransform::default().scaled(2.0, 2.0, p0);
590 // scaled once, but equal to 2 + 2
591 let scale_c = AffineTransform::default().scaled(4.0, 4.0, p0);
592 assert_ne!(&scale_a.0, &scale_b.0);
593 assert_relative_eq!(&scale_a, &scale_c);
594 }
595
596 #[test]
597 fn affine_transformed() {
598 let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, (0.0, 0.0));
599 let mut poly = wkt! { POLYGON((0.0 0.0,0.0 2.0,1.0 2.0)) };
600 poly.affine_transform_mut(&transform);
601
602 let expected = wkt! { POLYGON((2.0 2.0,2.0 6.0,4.0 6.0)) };
603 assert_eq!(expected, poly);
604 }
605 #[test]
606 fn affine_transformed_inverse() {
607 let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, (0.0, 0.0));
608 let tinv = transform.inverse().unwrap();
609 let identity = transform.compose(&tinv);
610 // test really only needs this, but let's be sure
611 assert!(identity.is_identity());
612
613 let mut poly = wkt! { POLYGON((0.0 0.0,0.0 2.0,1.0 2.0)) };
614 let expected = poly.clone();
615 poly.affine_transform_mut(&identity);
616 assert_eq!(expected, poly);
617 }
618 #[test]
619 fn test_affine_transform_getters() {
620 let transform = AffineTransform::new(10.0, 0.0, 400_000.0, 0.0, -10.0, 500_000.0);
621 assert_eq!(transform.a(), 10.0);
622 assert_eq!(transform.b(), 0.0);
623 assert_eq!(transform.xoff(), 400_000.0);
624 assert_eq!(transform.d(), 0.0);
625 assert_eq!(transform.e(), -10.0);
626 assert_eq!(transform.yoff(), 500_000.0);
627 }
628 #[test]
629 fn test_compose() {
630 let point = Point::new(1., 0.);
631
632 let translate = AffineTransform::translate(1., 0.);
633 let scale = AffineTransform::scale(4., 1., [0., 0.]);
634 let composed = translate.compose(&scale);
635
636 assert_eq!(point.affine_transform(&translate), Point::new(2., 0.));
637 assert_eq!(point.affine_transform(&scale), Point::new(4., 0.));
638 assert_eq!(
639 point.affine_transform(&translate).affine_transform(&scale),
640 Point::new(8., 0.)
641 );
642
643 assert_eq!(point.affine_transform(&composed), Point::new(8., 0.));
644 }
645}