package com.vividsolutions.jump.geom; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jump.I18N; /** * This class represents a affine transformation on the 2D Cartesian plane. * It can be used to transform a {@link Coordinate} or {@link Geometry}. * An affine transformation is a mapping of the 2D plane into itself * via a series of transformations of the following basic types: * <ul> * <li>reflection * <li>rotation * <li>scaling * <li>shearing * <li>translation * </ul> * In general, affine transformations preserve straightness and parallel lines, * but do not preserve distance or shape. * <p> * An affine transformation can be represented by a 3x3 * matrix in the following form: * <blockquote><pre> * T = | m00 m01 m02 | * | m10 m11 m12 | * | 0 0 1 | * </pre></blockquote> * A coordinate P = (x, y) can be transformed to a new coordinate P' = (x', y') * by representing it as a 3x1 matrix and using matrix multiplication to compute: * <blockquote><pre> * | x' | = T x | x | * | y' | | y | * | 1 | | 1 | * </pre></blockquote> * Affine transformations can be composed using the {@link #compose} method. * Composition is not commutative. * Composition is computed via multiplication of the * transformation matrices as follows: * <blockquote><pre> * A.compose(B) = T<sub>B</sub> x T<sub>A</sub> * </pre></blockquote> * This produces a transformation whose effect is that of A followed by B. * The methods {@link #reflect}, {@link #rotate}, {@link #scale}, {@link #shear}, and {@link #translate} * have the effect of composing a transformation of that type with * the transformation they are applied to. * * @author Martin Davis * * @deprecated see com.vividsolutions.jts.geom.util.AffineTransformation in JTS library * // AffineTransformationPlugIn has already been updated, it does not use this class any more [michaudm] */ public class AffineTransformation implements Cloneable, CoordinateFilter { /** * Creates a transformation for a reflection about the * line (x0,y0) - (x1,y1). * * @param x0 the x-ordinate of a point on the reflection line * @param y0 the y-ordinate of a point on the reflection line * @param x1 the x-ordinate of a another point on the reflection line * @param y1 the y-ordinate of a another point on the reflection line * @return a transformation for the reflection */ public static AffineTransformation reflectionInstance(double x0, double y0, double x1, double y1) { AffineTransformation trans = new AffineTransformation(); trans.setToReflection(x0, y0, x1, y1); return trans; } /** * Creates a transformation for a reflection about the * line (0,0) - (x,y). * * @param x the x-ordinate of a point on the reflection line * @param y the y-ordinate of a point on the reflection line * @return a transformation for the reflection */ public static AffineTransformation reflectionInstance(double x, double y) { AffineTransformation trans = new AffineTransformation(); trans.setToReflection(x, y); return trans; } /** * Creates a transformation for a rotation * about the origin * by an angle <i>theta</i>. * Positive angles correspond to a rotation * in the counter-clockwise direction. * * @param theta the rotation angle, in radians * @return a transformation for the rotation */ public static AffineTransformation rotationInstance(double theta) { return rotationInstance(Math.sin(theta), Math.cos(theta)); } /** * Creates a transformation for a rotation * by an angle <i>theta</i>, * specified by the sine and cosine of the angle. * This allows providing exact values for sin(theta) and cos(theta) * for the common case of rotations of multiples of quarter-circles. * * @param sinTheta the sine of the rotation angle * @param cosTheta the cosine of the rotation angle * @return a transformation for the rotation */ public static AffineTransformation rotationInstance(double sinTheta, double cosTheta) { AffineTransformation trans = new AffineTransformation(); trans.setToRotation(sinTheta, cosTheta); return trans; } public static AffineTransformation scaleInstance(double xScale, double yScale) { AffineTransformation trans = new AffineTransformation(); trans.setToScale(xScale, yScale); return trans; } public static AffineTransformation shearInstance(double xShear, double yShear) { AffineTransformation trans = new AffineTransformation(); trans.setToShear(xShear, yShear); return trans; } public static AffineTransformation translationInstance(double x, double y) { AffineTransformation trans = new AffineTransformation(); trans.setToTranslation(x, y); return trans; } // affine matrix entries // (bottom row is always [ 0 0 1 ]) private double m00; private double m01; private double m02; private double m10; private double m11; private double m12; /** * Constructs a new identity transformation * */ public AffineTransformation() { setToIdentity(); } /** * Constructs a new transformation whose * matrix has the specified values. * * @param matrix an array containing the 6 values { m00, m01, m02, m10, m11, m12 } * @throws NullPointerException if matrix is null * @throws ArrayIndexOutOfBoundsException if matrix is too small */ public AffineTransformation(double[] matrix) { m00 = matrix[0]; m01 = matrix[1]; m02 = matrix[2]; m10 = matrix[3]; m11 = matrix[4]; m12 = matrix[5]; } /** * Constructs a new transformation whose * matrix has the specified values. * * @param m00 the entry for the [0, 0] element in the transformation matrix * @param m01 the entry for the [0, 1] element in the transformation matrix * @param m02 the entry for the [0, 2] element in the transformation matrix * @param m10 the entry for the [1, 0] element in the transformation matrix * @param m11 the entry for the [1, 1] element in the transformation matrix * @param m12 the entry for the [1, 2] element in the transformation matrix */ public AffineTransformation(double m00, double m01, double m02, double m10, double m11, double m12) { setTransformation(m00, m01, m02, m10, m11, m12); } /** * Constructs a transformation which is * a copy of the given one. * * @param trans the transformation to copy */ public AffineTransformation(AffineTransformation trans) { setTransformation(trans); } /** * Constructs a transformation * which maps the given source * points into the given destination points. * * @param src0 source point 0 * @param src1 source point 1 * @param src2 source point 2 * @param dest0 the mapped point for source point 0 * @param dest1 the mapped point for source point 1 * @param dest2 the mapped point for source point 2 * */ public AffineTransformation(Coordinate src0, Coordinate src1, Coordinate src2, Coordinate dest0, Coordinate dest1, Coordinate dest2) { } /** * Sets this transformation to be the identity transformation. * The identity transformation has the matrix: * <blockquote><pre> * | 1 0 0 | * | 0 1 0 | * | 0 0 1 | * </pre></blockquote> * @return this transformation, with an updated matrix */ public AffineTransformation setToIdentity() { m00 = 1.0; m01 = 0.0; m02 = 0.0; m10 = 0.0; m11 = 1.0; m12 = 0.0; return this; } /** * Sets this transformation's matrix to have the given values. * * @param m00 the entry for the [0, 0] element in the transformation matrix * @param m01 the entry for the [0, 1] element in the transformation matrix * @param m02 the entry for the [0, 2] element in the transformation matrix * @param m10 the entry for the [1, 0] element in the transformation matrix * @param m11 the entry for the [1, 1] element in the transformation matrix * @param m12 the entry for the [1, 2] element in the transformation matrix * @return this transformation, with an updated matrix */ public AffineTransformation setTransformation(double m00, double m01, double m02, double m10, double m11, double m12) { this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m10 = m10; this.m11 = m11; this.m12 = m12; return this; } /** * Sets this transformation to be a copy of the given one * * @param trans a transformation to copy * @return this transformation, with an updated matrix */ public AffineTransformation setTransformation(AffineTransformation trans) { m00 = trans.m00; m01 = trans.m01; m02 = trans.m02; m10 = trans.m10; m11 = trans.m11; m12 = trans.m12; return this; } /** * Gets an array containing the entries * of the transformation matrix. * Only the 6 non-trivial entries are returned, * in the sequence: * <pre> * m00, m01, m02, m10, m11, m12 * </pre> * * @return an array of length 6 */ public double[] getMatrixEntries() { return new double[] { m00, m01, m02, m10, m11, m12 }; } /** * Computes the determinant of the transformation matrix. * The determinant is computed as: * <blockquote><pre> * | m00 m01 m02 | * | m10 m11 m12 | = m00 * m11 - m01 * m10 * | 0 0 1 | * </pre></blockquote> * If the determinant is zero, * the transform is singular (not invertible), * and operations which attempt to compute * an inverse will throw a <tt>NoninvertibleTransformException</tt>. * @return the determinant of the transformation * @see #getInverse() */ public double getDeterminant() { return m00 * m11 - m01 * m10; } /** * Computes the inverse of this transformation, if one * exists. * The inverse is the transformation which when * composed with this one produces the identity * transformation. * A transformation has an inverse if and only if it * is not singular (i.e. its * determinant is non-zero). * Geometrically, an transformation is non-invertible * if it maps the plane to a line or a point. * If no inverse exists this method * will throw a <tt>NoninvertibleTransformationException</tt>. * <p> * The matrix of the inverse is equal to the * inverse of the matrix for the transformation. * It is computed as follows: * <blockquote><pre> * 1 * inverse(A) = --- x adjoint(A) * det * * * = 1 | m11 -m01 m01*m12-m02*m11 | * --- x | -m10 m00 -m00*m12+m10*m02 | * det | 0 0 m00*m11-m10*m01 | * * * * = | m11/det -m01/det m01*m12-m02*m11/det | * | -m10/det m00/det -m00*m12+m10*m02/det | * | 0 0 1 | * * </pre></blockquote> * * @return a new inverse transformation * @throws NoninvertibleTransformationException * @see #getDeterminant() */ public AffineTransformation getInverse() throws NoninvertibleTransformationException { double det = getDeterminant(); if (det == 0) throw new NoninvertibleTransformationException(I18N.get("jump.geom.AffineTransformation.Transformation-is-non-invertible")); double im00 = m11 / det; double im10 = -m10 / det; double im01 = -m01 / det; double im11 = m00 / det; double im02 = (m01 * m12 - m02 * m11) / det; double im12 = (-m00 * m12 + m10 * m02) / det; return new AffineTransformation(im00, im01, im02, im10, im11, im12); } /** * Explicitly computes the math for a reflection. May not work. * @param x0 * @param y0 * @param x1 * @param y1 * @return this transformation, with an updated matrix */ public AffineTransformation setToReflectionBasic(double x0, double y0, double x1, double y1) { if (x0 == x1 && y0 == y1) { throw new IllegalArgumentException(I18N.get("jump.geom.AffineTransformation.Reflection-line-points-must-be-distinct")); } double dx = x1 - x0; double dy = y1 - y0; double d = Math.sqrt(dx * dx + dy * dy); double sin = dy / d; double cos = dx / d; double cs2 = 2 * sin * cos; double c2s2 = cos * cos - sin * sin; m00 = c2s2; m01 = cs2; m02 = 0.0; m10 = cs2; m11 = -c2s2; m12 = 0.0; return this; } public AffineTransformation setToReflection(double x0, double y0, double x1, double y1) { if (x0 == x1 && y0 == y1) { throw new IllegalArgumentException(I18N.get("jump.geom.AffineTransformation.Reflection-line-points-must-be-distinct")); } // translate line vector to origin setToTranslation(-x0, -y0); // rotate vector to positive x axis direction double dx = x1 - x0; double dy = y1 - y0; double d = Math.sqrt(dx * dx + dy * dy); double sin = dy / d; double cos = dx / d; rotate(-sin, cos); // reflect about the x axis scale(1, -1); // rotate back rotate(sin, cos); // translate back translate(x0, y0); return this; } /** * Sets this transformation to be a reflection * about the line defined by vector (x,y). * The transformation for a reflection * is computed by: * <blockquote><pre> * d = sqrt(x<sup>2</sup> + y<sup>2</sup>) * sin = x / d; * cos = x / d; * * T<sub>ref</sub> = T<sub>rot(sin, cos)</sub> x T<sub>scale(1, -1)</sub> x T<sub>rot(-sin, cos)</sub * </pre></blockquote> * * @param x the x-component of the reflection line vector * @param y the y-component of the reflection line vector * @return this transformation, with an updated matrix */ public AffineTransformation setToReflection(double x, double y) { if (x == 0.0 && y == 0.0) { throw new IllegalArgumentException(I18N.get("jump.geom.AffineTransformation.Reflection-vector-must-be-non-zero")); } // rotate vector to positive x axis direction double d = Math.sqrt(x * x + y * y); double sin = y / d; double cos = x / d; rotate(-sin, cos); // reflect about the x-axis scale(1, -1); // rotate back rotate(sin, cos); return this; } /** * Sets this transformation to be a rotation. * A positive rotation angle corresponds * to a counter-clockwise rotation. * The transformation matrix for a rotation * by an angle <tt>theta</tt> * has the value: * <blockquote><pre> * | cos(theta) -sin(theta) 0 | * | sin(theta) cos(theta) 0 | * | 0 0 1 | * </pre></blockquote> * * @param theta the rotation angle, in radians * @return this transformation, with an updated matrix */ private AffineTransformation setToRotation(double theta) { setToRotation(Math.sin(theta), Math.cos(theta)); return this; } /** * Sets this transformation to be a rotation * by specifying the sin and cos of the rotation angle directly. * The transformation matrix for the rotation * has the value: * <blockquote><pre> * | cosTheta -sinTheta 0 | * | sinTheta cosTheta 0 | * | 0 0 1 | * </pre></blockquote> * * @param sinTheta the sine of the rotation angle * @param cosTheta the cosine of the rotation angle * @return this transformation, with an updated matrix */ public AffineTransformation setToRotation(double sinTheta, double cosTheta) { m00 = cosTheta; m01 = -sinTheta; m02 = 0.0; m10 = sinTheta; m11 = cosTheta; m12 = 0.0; return this; } /** * Sets this transformation to be a scaling. * The transformation matrix for a scale * has the value: * <blockquote><pre> * | xScale 0 dx | * | 1 yScale dy | * | 0 0 1 | * </pre></blockquote> * * @param xScale the amount to scale x-ordinates by * @param yScale the amount to scale y-ordinates by * @return this transformation, with an updated matrix */ public AffineTransformation setToScale(double xScale, double yScale) { m00 = xScale; m01 = 0.0; m02 = 0.0; m10 = 0.0; m11 = yScale; m12 = 0.0; return this; } /** * Sets this transformation to be a shear. * The transformation matrix for a shear * has the value: * <blockquote><pre> * | 1 xShear 0 | * | yShear 1 0 | * | 0 0 1 | * </pre></blockquote> * Note that a shear of (1, 1) is <i>not</i> * equal to shear(1, 0) composed with shear(0, 1). * Instead, shear(1, 1) corresponds to a mapping onto the * line x = y. * * @param xShear the x component to shear by * @param yShear the y component to shear by * @return this transformation, with an updated matrix */ public AffineTransformation setToShear(double xShear, double yShear) { m00 = 1.0; m01 = xShear; m02 = 0.0; m10 = yShear; m11 = 1.0; m12 = 0.0; return this; } /** * Sets this transformation to be a translation. * For a translation by the vector (x, y) * the transformation matrix has the value: * <blockquote><pre> * | 1 0 dx | * | 1 0 dy | * | 0 0 1 | * </pre></blockquote> * @param dx the x component to translate by * @param dy the y component to translate by * @return this transformation, with an updated matrix */ public AffineTransformation setToTranslation(double dx, double dy) { m00 = 1.0; m01 = 0.0; m02 = dx; m10 = 0.0; m11 = 1.0; m12 = dy; return this; } /** * Updates the value of this transformation * to that of a reflection transformation composed * with the current value. * * @param x0 the x-ordinate of a point on the line to reflect around * @param y0 the y-ordinate of a point on the line to reflect around * @param x1 the x-ordinate of a point on the line to reflect around * @param y1 the y-ordinate of a point on the line to reflect around * @return this transformation, with an updated matrix */ public AffineTransformation reflect(double x0, double y0, double x1, double y1) { compose(reflectionInstance(x0, y0, x1, y1)); return this; } /** * Updates the value of this transformation * to that of a reflection transformation composed * with the current value. * * @param x the x-ordinate of the line to reflect around * @param y the y-ordinate of the line to reflect around * @return this transformation, with an updated matrix */ public AffineTransformation reflect(double x, double y) { compose(reflectionInstance(x, y)); return this; } /** * Updates the value of this transformation * to that of a rotation transformation composed * with the current value. * * @param theta the angle to rotate by * @return this transformation, with an updated matrix */ public AffineTransformation rotate(double theta) { compose(rotationInstance(theta)); return this; } /** * Updates the value of this transformation * to that of a rotation transformation composed * with the current value. * * @param sinTheta the sine of the angle to rotate by * @param cosTheta the cosine of the angle to rotate by * @return this transformation, with an updated matrix */ public AffineTransformation rotate(double sinTheta, double cosTheta) { compose(rotationInstance(sinTheta, cosTheta)); return this; } /** * Updates the value of this transformation * to that of a scale transformation composed * with the current value. * * @param xScale the value to scale by in the x direction * @param yScale the value to scale by in the y direction * @return this transformation, with an updated matrix */ public AffineTransformation scale(double xScale, double yScale) { compose(scaleInstance(xScale, yScale)); return this; } /** * Updates the value of this transformation * to that of a shear transformation composed * with the current value. * * @param xShear the value to shear by in the x direction * @param yShear the value to shear by in the y direction * @return this transformation, with an updated matrix */ public AffineTransformation shear(double xShear, double yShear) { compose(shearInstance(xShear, yShear)); return this; } /** * Updates the value of this transformation * to that of a translation transformation composed * with the current value. * * @param x the value to translate by in the x direction * @param y the value to translate by in the y direction * @return this transformation, with an updated matrix */ public AffineTransformation translate(double x, double y) { compose(translationInstance(x, y)); return this; } /** * Composes the given {@link AffineTransformation} * with this transformation. * This produces a transformation whose effect * is equal to applying this transformation * followed by the argument transformation. * Mathematically, * <blockquote><pre> * A.compose(B) = T<sub>B</sub> x T<sub>A</sub> * </pre></blockquote> * * @param trans an affine transformation * @return this transformation, with an updated matrix */ public AffineTransformation compose(AffineTransformation trans) { double mp00 = trans.m00 * m00 + trans.m01 * m10; double mp01 = trans.m00 * m01 + trans.m01 * m11; double mp02 = trans.m00 * m02 + trans.m01 * m12 + trans.m02; double mp10 = trans.m10 * m00 + trans.m11 * m10; double mp11 = trans.m10 * m01 + trans.m11 * m11; double mp12 = trans.m10 * m02 + trans.m11 * m12 + trans.m12; m00 = mp00; m01 = mp01; m02 = mp02; m10 = mp10; m11 = mp11; m12 = mp12; return this; } /** * Composes this transformation * with the given {@link AffineTransformation}. * This produces a transformation whose effect * is equal to applying the argument transformation * followed by this transformation. * Mathematically, * <blockquote><pre> * A.composeBefore(B) = T<sub>A</sub> x T<sub>B</sub> * </pre></blockquote> * * @param trans an affine transformation * @return this transformation, with an updated matrix */ public AffineTransformation composeBefore(AffineTransformation trans) { double mp00 = m00 * trans.m00 + m01 * trans.m10; double mp01 = m00 * trans.m01 + m01 * trans.m11; double mp02 = m00 * trans.m02 + m01 * trans.m12 + m02; double mp10 = m10 * trans.m00 + m11 * trans.m10; double mp11 = m10 * trans.m01 + m11 * trans.m11; double mp12 = m10 * trans.m02 + m11 * trans.m12 + m12; m00 = mp00; m01 = mp01; m02 = mp02; m10 = mp10; m11 = mp11; m12 = mp12; return this; } public Coordinate transform(Coordinate src, Coordinate dest) { double xp = m00 * src.x + m01 * src.y + m02; double yp = m10 * src.x + m11 * src.y + m12; dest.x = xp; dest.y = yp; return dest; } public CoordinateSequence transform(CoordinateSequence seq) { for (int i = 0; i < seq.size(); i++) { double xp = m00 * seq.getOrdinate(i, 0) + m01 * seq.getOrdinate(i, 1) + m02; double yp = m10 * seq.getOrdinate(i, 0) + m11 * seq.getOrdinate(i, 1) + m12; seq.setOrdinate(i, 0, xp); seq.setOrdinate(i, 1, yp); } return seq; } public void filter(Coordinate pt) { double xp = m00 * pt.x + m01 * pt.y + m02; double yp = m10 * pt.x + m11 * pt.y + m12; pt.x = xp; pt.y = yp; } /** * Tests if this transformation is the identity transformation. * * @return true if this is the identity transformation */ public boolean isIdentity() { return (m00 == 1 && m01 == 0 && m02 == 0 && m10 == 0 && m11 == 1 && m12 == 0); } /** * Tests if an object is an * <tt>AffineTransformation</tt> * and has the same matrix as * this transformation. * * @param obj an object to test * @return true if the given object is equal to this object */ public boolean equals(Object obj) { if (obj instanceof AffineTransformation) return false; AffineTransformation trans = (AffineTransformation) obj; return m00 == trans.m00 && m01 == trans.m01 && m02 == trans.m02 && m10 == trans.m10 && m11 == trans.m11 && m12 == trans.m12; } /** * Gets a text representation of this transformation. * The string is of the form: * <pre> * AffineTransformation[[m00, m01, m02], [m10, m11, m12]] * </pre> * * @return a string representing this transformation * */ public String toString() { return "AffineTransformation[[" + m00 + ", " + m01 + ", " + m02 + "], [" + m10 + ", " + m11 + ", " + m12 + "]]"; } /** * Clones this transformation * * @return a copy of this transformation */ public Object clone() { return new AffineTransformation(this); } }