Affine

repository·main·Indexed 41 days ago

https://github.com/rasterio/affine

A library providing 3x3 augmented matrices for 2D affine transformations of a plane. Commonly used in GIS to map image coordinates to world coordinates, it supports translations, scaling, shearing, and rotations. It includes specialized methods for GDAL GeoTransform conversion via Affine.from_gdal() and to_gdal(), as well as integration with Shapely via to_shapely().

Tokens
2.7K
Snippets
9
Records
11
Agent score
2%

What's inside Affine

  1. Apply and combine affine transformations

    main

    Affine matrices can be used to transform coordinates or combine multiple transformations together.

    Transform coordinates

    Use the @ matrix multiplier operator to apply an Affine matrix to an (x, y) tuple. This returns the transformed coordinates (x', y').

    Note: For versions prior to 3.0.0, use the * operator instead of @.

    Combine transformations

    You can multiply two Affine matrices together using the @ operator to create a single matrix that represents the combined transformation.

    # Transform a point
    point = (1.0, 1.0)
    transformed_point = Affine.translation(1.0, 5.0) @ point
    
    # Combine transformations
    combined_matrix = Affine.translation(1.0, 5.0) @ Affine.rotation(45.0)
    from affine import Affine
    
    # Apply transformation to a coordinate tuple
    # Result: (2.0, 6.0)
    print(Affine.translation(1.0, 5.0) @ (1.0, 1.0))
    
    # Combine transformations
    # Result: Affine(0.7071067811865476, -0.7071067811865475, 1.0, 0.7071067811865475, 0.7071067811865476, 5.0)
    combined = Affine.translation(1.0, 5.0) @ Affine.rotation(45.0)
  2. Convert GDAL GeoTransforms to Affine matrices

    main

    In GIS workflows, georeferenced raster datasets use affine transformations to map image coordinates to world coordinates.

    You can convert a GDAL GeoTransform (a sequence of 6 numbers) into an Affine object using the Affine.from_gdal() class method.

    The GDAL sequence format is:

    1. X offset
    2. X pixel size
    3. X rotation (usually 0)
    4. Y offset
    5. Y rotation (usually 0)
    6. Y pixel size

    To perform the reverse transformation (mapping world coordinates back to image coordinates), use the ~ inverse operator.

    from affine import Affine
    
    # Example GDAL geotransform
    geotransform = (-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
    
    # Create forward transformation
    fwd = Affine.from_gdal(*geotransform)
    
    # Map image coordinates (col, row) to world coordinates
    # Result: (-237481.5, 195036.4)
    world_coords = fwd @ (0, 100)
    
    # Get the reverse transformation (world to image)
    rev = ~fwd
    
    # Verify inverse: mapping world back to image
    # Result: (0.0, 99.99999999999999)
    print(rev @ world_coords)
  3. Create and manipulate Affine transformations

    main

    The Affine class represents a 2D affine transformation matrix. It can perform translations, scales/flips, shears, and rotations. Transformations are represented by a 3x3 augmented matrix where the last row is always [0, 0, 1].

    Key operations:

    • Composition: Use the @ operator (matmul) to compose two Affine objects or to apply a transform to a vector/shape.
    • Inversion: Use the ~ operator to get the inverse transform. This raises TransformNotInvertibleError if the transform is degenerate.
    • Vector Application: Applying an Affine object to a (x, y) tuple returns a new (x, y) tuple.
    • Identity: Use Affine.identity() for a transform that does nothing.
    from affine import Affine
    
    # Create a translation
    t = Affine.translation(10.0, 20.0)
    
    # Apply to a point
    point = (5.0, 5.0)
    new_point = t @ point  # Returns (15.0, 25.0)
    
    # Compose transforms
    scale = Affine.scale(2.0)
    combined = t @ scale
    
    # Invert
    inverse = ~t
  4. Create affine transformation matrices

    main

    You can create 3x3 augmented affine transformation matrices for 2D plane transformations using the affine.Affine constructor or several specialized class methods.

    The matrix structure is:

    | x' | | y' | | 1 |

    Represented as:

    | a b c | | d e f | | 0 0 1 |

    Common methods for creating matrices include:

    • Affine.identity(): Creates an identity matrix.
    • Affine.translation(xoff, yoff): Creates a translation matrix.
    • Affine.scale(s): Creates a scaling matrix.
    • Affine.shear(x_deg, y_deg): Creates a shear matrix (using decimal degrees).
    • Affine.rotation(deg): Creates a rotation matrix (using decimal degrees).
    • Affine(a, b, c, d, e, f): Direct constructor for specific matrix coefficients.
    from affine import Affine
    
    # Create specific transformations
    identity = Affine.identity()
    translate = Affine.translation(1.0, 5.0)
    scale = Affine.scale(2.0)
    shear = Affine.shear(45.0, 45.0)
    rotate = Affine.rotation(45.0)
    
    # Direct constructor
    custom = Affine(1.0, 0.0, 1.0, 0.0, 1.0, 5.0)
  5. Invert an affine transformation

    main

    To obtain the reverse transformation (e.g., mapping from world coordinates back to image/pixel coordinates), use the ~ unary operator on an Affine instance. This computes the matrix inverse.

    from affine import Affine
    
    fwd = Affine.translation(1.0, 5.0)
    rev = ~fwd
    
    # Applying the inverse to a transformed point returns the original point
    # (allowing for floating point precision errors)
    print(rev @ (fwd @ (1.0, 1.0)))
  6. Load and dump Affine transforms from world files

    main

    Use these utilities to handle

    from affine import loadsw, dumpsw
    
    # Load from a world file string (6 space-separated floats)
    # Note: loadsw handles the center-to-corner coordinate translation
    t = loadsw("0.0 1.0 0.0 0.0 1.0 0.0")
    
    # Dump to a world file string
    world_file_str = dumpsw(t)
  7. Convert Affine transforms to GDAL or Shapely formats

    main

    To interface with other GIS libraries, use the following methods:

    • to_gdal(): Returns a tuple in the order expected by GDAL's SetGeoTransform(): (c, a, b, f, d, e).
    • to_shapely(): Returns a tuple for Shapely's affinity module: (a, b, d, e, c, f).
    from affine import Affine
    
    t = Affine.translation(1.0, 2.0)
    
    gdal_params = t.to_gdal()
    shapely_params = t.to_shapely()
  8. Initialize Affine transforms using class methods

    main

    The Affine class provides several factory methods to create common transformations:

    • Affine.identity(): Returns the identity transform.
    • Affine.translation(xoff, yoff): Creates a translation by an offset vector.
    • Affine.scale(*scaling): Creates a scaling transform. Accepts a single scalar (uniform scaling) or two floats (independent x and y scaling).
    • Affine.shear(x_angle=0.0, y_angle=0.0): Creates a shear transform using angles in degrees.
    • Affine.rotation(angle, pivot=None): Creates a rotation transform in degrees counter-clockwise. If pivot (a (px, py) sequence) is provided, it rotates around that point; otherwise, it rotates around (0.0, 0.0).
    • Affine.permutation(*scaling): Returns a permutation matrix (swaps axes).
    • Affine.from_gdal(c, a, b, f, d, e): Initializes using GDAL's GetGeoTransform() coefficient order.
    • Affine(*a, b, c, d, e, f): Standard constructor using matrix coefficients.
    from affine import Affine
    
    # Scaling
    s = Affine.scale(2.0, 3.0)
    
    # Rotation around a point
    r = Affine.rotation(45.0, pivot=(10.0, 10.0))
    
    # GDAL compatibility
    from_gdal = Affine.from_gdal(0.0, 1.0, 0.0, 0.0, 1.0, 0.0)
  9. Inspect Affine transform properties

    main

    Use these properties to analyze the characteristics of an Affine object:

    • determinant: The area scaling factor. A determinant of 0.0 indicates a degenerate transform.
    • is_identity: True if the transform is the identity matrix (within EPSILON).
    • is_degenerate: True if the transform collapses a shape to zero area.
    • is_proper: True if the transform has a positive determinant (no reflection).
    • is_rectilinear: True if the transform keeps shapes axis-aligned.
    • is_conformal: True if angles are preserved (no shear).
    • is_orthonormal: True if the transform is a rigid motion (no scaling or shear).
    • rotation_angle: The rotation angle in degrees (assuming $M = RS$). Raises UndefinedRotationError if the rotation cannot be computed.
    • eccentricity: The eccentricity of an ellipse under this transformation.
    • xoff / yoff: Aliases for c and f (translation components).
  10. Configure global epsilon for approximate equality

    main

    The set_epsilon(epsilon) function sets the global absolute error value used for approximate floating-point comparisons (e.g., in almost_equals or is_identity).

    • The default is 1e-5.
    • You may need a larger epsilon when working with very large coordinate values, or a smaller one for very small values near zero.
    • This affects the global affine.EPSILON and affine.EPSILON2 variables.
    import affine
    
    # Set a custom precision for comparisons
    affine.set_epsilon(1e-8)
  11. Handle Affine transformation errors

    main

    When working with transformations, be prepared to catch these exceptions:

    • AffineError: Base class for all errors in the package.
    • TransformNotInvertibleError: Raised when attempting to invert (~) a degenerate transform (one with a determinant of 0).
    • UndefinedRotationError: Raised when attempting to access .rotation_angle on a transform where the rotation cannot be uniquely determined.