Skip to content

Commit 263caab

Browse files
authored
Merge pull request #1092 from urschrei/affineinverse
Add inverse method to AffineTransform
2 parents 173085f + e69c60b commit 263caab

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

geo/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
* Add an `inverse` method to `AffineTransform`
6+
* <https://github.com/georust/geo/pull/1092>
57
* Fix `Densify` trait to avoid panic with empty line string.
68
* <https://github.com/georust/geo/pull/1082>
79
* Add `DensifyHaversine` trait to densify spherical line geometry.

geo/src/algorithm/affine_ops.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use num_traits::ToPrimitive;
2+
13
use crate::{Coord, CoordFloat, CoordNum, MapCoords, MapCoordsInPlace};
2-
use std::fmt;
4+
use std::{fmt, ops::Mul, ops::Neg};
35

46
/// Apply an [`AffineTransform`] like [`scale`](AffineTransform::scale),
57
/// [`skew`](AffineTransform::skew), or [`rotate`](AffineTransform::rotate) to a
@@ -277,6 +279,41 @@ impl<T: CoordNum> AffineTransform<T> {
277279
}
278280
}
279281

282+
impl<T: CoordNum + Neg> AffineTransform<T> {
283+
/// Return the inverse of a given transform. Composing a transform with its inverse yields
284+
/// the [identity matrix](Self::identity)
285+
#[must_use]
286+
pub fn inverse(&self) -> Option<Self>
287+
where
288+
<T as Neg>::Output: Mul<T>,
289+
<<T as Neg>::Output as Mul<T>>::Output: ToPrimitive,
290+
{
291+
let a = self.0[0][0];
292+
let b = self.0[0][1];
293+
let xoff = self.0[0][2];
294+
let d = self.0[1][0];
295+
let e = self.0[1][1];
296+
let yoff = self.0[1][2];
297+
298+
let determinant = a * e - b * d;
299+
300+
if determinant == T::zero() {
301+
return None; // The matrix is not invertible
302+
}
303+
let inv_det = T::one() / determinant;
304+
305+
// If conversion of either the b or d matrix value fails, bail out
306+
Some(Self::new(
307+
e * inv_det,
308+
T::from(-b * inv_det)?,
309+
(b * yoff - e * xoff) * inv_det,
310+
T::from(-d * inv_det)?,
311+
a * inv_det,
312+
(d * xoff - a * yoff) * inv_det,
313+
))
314+
}
315+
}
316+
280317
impl<T: CoordNum> fmt::Debug for AffineTransform<T> {
281318
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282319
f.debug_struct("AffineTransform")
@@ -436,4 +473,17 @@ mod tests {
436473
let expected = polygon![(x: 1.0, y: 1.0), (x: 1.0, y: 5.0), (x: 3.0, y: 5.0)];
437474
assert_eq!(expected, poly);
438475
}
476+
#[test]
477+
fn affine_transformed_inverse() {
478+
let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, (0.0, 0.0));
479+
let tinv = transform.inverse().unwrap();
480+
let identity = transform.compose(&tinv);
481+
// test really only needs this, but let's be sure
482+
assert!(identity.is_identity());
483+
484+
let mut poly = polygon![(x: 0.0, y: 0.0), (x: 0.0, y: 2.0), (x: 1.0, y: 2.0)];
485+
let expected = poly.clone();
486+
poly.affine_transform_mut(&identity);
487+
assert_eq!(expected, poly);
488+
}
439489
}

0 commit comments

Comments
 (0)