Class AffineTransformationBuilder

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12  
 13 package org.locationtech.jts.geom.util;
 14  
 15 import org.locationtech.jts.geom.Coordinate;
 16 import org.locationtech.jts.math.Matrix;
 17  
 18 /**
 19  * Builds an {@link AffineTransformation} defined by a set of control vectors. 
 20  * A control vector consists of a source point and a destination point, 
 21  * which is the image of the source point under the desired transformation.
 22  * <p>
 23  * A transformation is well-defined 
 24  * by a set of three control vectors 
 25  * if and only if the source points are not collinear. 
 26  * (In particular, the degenerate situation
 27  * where two or more source points are identical will not produce a well-defined transformation).
 28  * A well-defined transformation exists and is unique.
 29  * If the control vectors are not well-defined, the system of equations
 30  * defining the transformation matrix entries is not solvable,
 31  * and no transformation can be determined.
 32  * <p>
 33  * No such restriction applies to the destination points.
 34  * However, if the destination points are collinear or non-unique,
 35  * a non-invertible transformations will be generated.
 36  * <p>
 37  * This technique of recovering a transformation
 38  * from its effect on known points is used in the Bilinear Interpolated Triangulation
 39  * algorithm for warping planar surfaces.
 40  *
 41  * @author Martin Davis
 42  */
 43 public class AffineTransformationBuilder
 44 {
 45   private Coordinate src0;
 46   private Coordinate src1;
 47   private Coordinate src2;
 48   private Coordinate dest0;
 49   private Coordinate dest1;
 50   private Coordinate dest2;
 51   
 52   // the matrix entries for the transformation
 53   private double m00m01m02m10m11m12;
 54   
 55  
 56   /**
 57    * Constructs a new builder for
 58    * the transformation defined by the given 
 59    * set of control point mappings.
 60    * 
 61    * @param src0 a control point
 62    * @param src1 a control point
 63    * @param src2 a control point
 64    * @param dest0 the image of control point 0 under the required transformation
 65    * @param dest1 the image of control point 1 under the required transformation
 66    * @param dest2 the image of control point 2 under the required transformation
 67    */
 68   public AffineTransformationBuilder(Coordinate src0,
 69       Coordinate src1,
 70       Coordinate src2,
 71       Coordinate dest0,
 72       Coordinate dest1,
 73       Coordinate dest2)
 74   {
 75     this.src0 = src0;
 76     this.src1 = src1;
 77     this.src2 = src2;
 78     this.dest0 = dest0;
 79     this.dest1 = dest1;
 80     this.dest2 = dest2;
 81   }
 82     
 83   /**
 84    * Computes the {@link AffineTransformation}
 85    * determined by the control point mappings,
 86    * or <code>null</code> if the control vectors do not determine a well-defined transformation.
 87    * 
 88    * @return an affine transformation, or null if the control vectors do not determine a well-defined transformation
 89    */
 90   public AffineTransformation getTransformation()
 91   {
 92       // compute full 3-point transformation
 93     boolean isSolvable = compute();
 94     if (isSolvable)
 95       return new AffineTransformation(m00, m01, m02, m10, m11, m12);
 96     return null;
 97   }
 98     
 99   /**
100    * Computes the transformation matrix by 
101    * solving the two systems of linear equations
102    * defined by the control point mappings,
103    * if this is possible.
104    * 
105    * @return true if the transformation matrix is solvable
106    */
107   private boolean compute()
108   {
109     double[] bx = new double[] { dest0.x, dest1.x, dest2.x };
110     double[] row0 = solve(bx);
111     if (row0 == nullreturn false;
112     m00 = row0[0];
113     m01 = row0[1];
114     m02 = row0[2];
115     
116     double[] by = new double[] { dest0.y, dest1.y, dest2.y };
117     double[] row1 = solve(by);
118     if (row1 == nullreturn false;
119     m10 = row1[0];
120     m11 = row1[1];
121     m12 = row1[2];
122     return true;
123   }
124  
125   /**
126    * Solves the transformation matrix system of linear equations
127    * for the given right-hand side vector.
128    * 
129    * @param b the vector for the right-hand side of the system
130    * @return the solution vector, or <code>null</code> if no solution could be determined
131    */
132   private double[] solve(double[] b)
133   {
134     double[][] a = new double[][] {
135         { src0.x, src0.y, 1 },
136         { src1.x, src1.y, 1},
137         { src2.x, src2.y, 1}
138     };
139     return Matrix.solve(a, b);
140   }
141 }
142