Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

affine-rs

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

Installation

Add the crate with Cargo:

cargo add affine-rs

Or 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.

Quick start

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.

Creating transformations

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.

Transforming points

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]]);

Composing transformations

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.

Inverting a transformation

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(())
}

Inspecting geometric properties

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 GeoTransform interoperability

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 File interoperability

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(())
}

Scope

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.

Documentation and source

License

affine-rs is distributed under the BSD-3-Clause license. See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages