affine-rs is a dependency-free Rust library for two-dimensional affine
transformations. It is a Rust-native rewrite of the mathematical core of
affine-py.
The crate represents transformations using the augmented matrix below.
| a b c |
| d e f |
| 0 0 1 |
It maps an input point [x, y] to:
x' = a*x + b*y + c
y' = d*x + e*y + f
Add the crate with Cargo:
cargo add affine-rsOr add it to Cargo.toml:
[dependencies]
affine-rs = "0.1.0"The package name contains a hyphen, while the Rust crate name uses an underscore:
use affine_rs::Affine;affine-rs requires Rust 1.85 or later.
use affine_rs::Affine;
let transform = Affine::translation(10.0, 20.0)
.compose(Affine::scale(2.0, 2.0));
let point = transform.transform_point([3.0, 4.0]);
assert_eq!(point, [16.0, 28.0]);compose() applies its right-hand argument first. The example therefore
scales the point before translating it.
use affine_rs::Affine;
let identity = Affine::IDENTITY;
let translation = Affine::translation(10.0, 20.0);
let scale = Affine::scale(2.0, 3.0);
let uniform_scale = Affine::uniform_scale(2.0);
let rotation = Affine::rotation(45.0);
let pivot_rotation = Affine::rotation_around(90.0, [10.0, 10.0]);
let shear = Affine::shear(10.0, 5.0);
let permutation = Affine::permutation();
assert!(identity.is_identity());
assert_eq!(translation.transform_point([1.0, 2.0]), [11.0, 22.0]);Angles are expressed in degrees. Positive rotation angles rotate counter-clockwise.
Transform a single [x, y] point using a named method or the * operator:
use affine_rs::Affine;
let transform = Affine::translation(10.0, 20.0);
assert_eq!(transform.transform_point([1.0, 2.0]), [11.0, 22.0]);
assert_eq!(transform * [1.0, 2.0], [11.0, 22.0]);Transform a mutable point slice in place:
use affine_rs::Affine;
let mut points = [[0.0, 0.0], [1.0, 2.0]];
Affine::translation(10.0, 20.0).transform_points_in_place(&mut points);
assert_eq!(points, [[10.0, 20.0], [11.0, 22.0]]);a.compose(b) and a * b both apply b first and a second:
use affine_rs::Affine;
let translation = Affine::translation(10.0, 20.0);
let scale = Affine::uniform_scale(2.0);
let composed = translation.compose(scale);
let multiplied = translation * scale;
assert_eq!(composed, multiplied);
assert_eq!(composed.transform_point([3.0, 4.0]), [16.0, 28.0]);Changing the order generally changes the result because matrix multiplication is not commutative.
inverse() returns an error for degenerate or non-finite transformations:
use affine_rs::{Affine, AffineError};
fn main() -> Result<(), AffineError> {
let forward = Affine::translation(10.0, 20.0)
.compose(Affine::scale(2.0, 3.0));
let reverse = forward.inverse()?;
let source = [3.0, 4.0];
let transformed = forward.transform_point(source);
let restored = reverse.transform_point(transformed);
assert!((restored[0] - source[0]).abs() < 1.0e-10);
assert!((restored[1] - source[1]).abs() < 1.0e-10);
Ok(())
}use affine_rs::Affine;
let transform = Affine::rotation(30.0);
assert!((transform.determinant() - 1.0).abs() < 1.0e-10);
assert!(transform.is_proper());
assert!(!transform.is_degenerate());
assert!(transform.is_conformal());
assert!(transform.is_orthonormal());
assert!((transform.rotation_angle().unwrap() - 30.0).abs() < 1.0e-10);The crate also provides is_identity(), is_rectilinear(), scaling(),
eccentricity(), column_vectors(), and epsilon-based approx_eq().
GDAL uses a different order for the same six affine coefficients.
from_gdal() and to_gdal() perform the ordering conversion:
use affine_rs::Affine;
let geotransform = [
-237_481.5,
425.0,
0.0,
237_536.4,
0.0,
-425.0,
];
let transform = Affine::from_gdal(geotransform);
assert_eq!(transform.to_gdal(), geotransform);
assert_eq!(transform.transform_point([0.0, 100.0]), [-237_481.5, 195_036.4]);to_shapely() returns the six coefficients in the order expected by
Shapely-compatible affine APIs.
World Files store six coefficients using pixel-center coordinates. The parser and serializer automatically convert between pixel-center and pixel-corner coordinates:
use affine_rs::{Affine, AffineError};
fn main() -> Result<(), AffineError> {
let world_file = "1.0\n0.0\n0.0\n-1.0\n100.5\n199.5\n";
let transform = Affine::from_world_file(world_file)?;
assert_eq!(
transform,
Affine::new(1.0, 0.0, 100.0, 0.0, -1.0, 200.0)
);
assert_eq!(transform.to_world_file(), world_file);
Ok(())
}This crate handles two-dimensional affine mathematics only. It does not provide:
- coordinate reference system definitions;
- transformations between different CRSs;
- raster or vector file I/O;
- raster reprojection or resampling;
- GCP, TPS, or RPC georeferencing models.
These operations belong in higher-level GIS libraries. affine-rs is intended
to be a small building block for pixel-to-map and planar coordinate
transformations.
affine-rs is distributed under the BSD-3-Clause license. See
LICENSE.