/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.referencing.operation.builder; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.vecmath.MismatchedSizeException; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.geometry.MismatchedReferenceSystemException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.operation.MathTransform; import org.geotools.referencing.operation.matrix.GeneralMatrix; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.referencing.operation.transform.ProjectiveTransform; /** * Builder for affine transformation with possibility to set several constrains * for affine parameters that will be respected during calculation. This is convenient * for example to use when you want affine transformation with skew parameter equal to zero. * Development carried out thanks to R&D grant DC08P02OUK006 - Old Maps Online * (www.oldmapsonline.org) from Ministry of Culture of the Czech Republic * * @author jezekjan * @since * * @source $URL$ * @version $Id$ */ public class AdvancedAffineBuilder extends MathTransformBuilder { /**mark for key to specify sx - scale in x constrain */ public static final String SX = "sx"; /**mark for key to specify sy - scale in y constrain */ public static final String SY = "sy"; /**mark for key to specify sxy - skew constrain */ public static final String SXY = "sxy"; /**mark for key to specify phix - rotation constrain */ public static final String PHIX = "phix"; /**mark for key to specify phix - rotation constrain */ public static final String PHIY = "phiy"; /**mark for key to specify tx - translation in x constrain */ public static final String TX = "tx"; /**mark for key to specify ty - translation in y constrain */ public static final String TY = "ty"; /** translation in x */ private double tx; /** translation in y */ private double ty; /** scale in x */ private double sx; /** scale in y */ private double sy; /** x rotation in radians */ private double phix; /** x rotation in radians */ private double phiy; /** max difference for iteration */ private double dif = 0.0000001; /** max number of steps for iteration */ private int steps = 100; /** Map of constrains - parameter name as key and its required value*/ private Map<String, Double> valueConstrain = new HashMap<String, Double>(); /** Map of constrains - parameters (represented by string) are equal to each other*/ private Map<String, String> equalConstrain = new HashMap<String, String>(); /**Affine transformation for approximate values*/ private final AffineTransform2D affineTrans; /** * Constructs builder from set of GCPs * @param vectors GCPs */ public AdvancedAffineBuilder(final List<MappedPosition> vectors) throws MismatchedSizeException, MismatchedDimensionException, MismatchedReferenceSystemException, FactoryException { /** * use constructor with approximate values taken from 6 parameters of affine transform */ this(vectors, (AffineTransform2D) (new AffineTransformBuilder(vectors).getMathTransform())); } /** * Constructs affine transform from GCPs and approximate values for calculation. This constructor * should be used when the default calculation is divergating. * @param vectors GCPs * @param affineTrans approximate affine transformation * @throws MismatchedSizeException * @throws MismatchedDimensionException * @throws MismatchedReferenceSystemException * @throws FactoryException */ public AdvancedAffineBuilder(final List<MappedPosition> vectors, AffineTransform2D affineTrans) throws MismatchedSizeException, MismatchedDimensionException, MismatchedReferenceSystemException, FactoryException { super.setMappedPositions(vectors); /** * sets approximate values */ this.affineTrans = affineTrans; AffineToGeometric a2g = new AffineToGeometric(affineTrans); sx = a2g.getXScale(); sy = a2g.getYScale(); phix = a2g.getXRotation(); phiy = a2g.getYRotation(); tx = a2g.getXTranslate(); ty = a2g.getYTranslate(); } /** * Sets constrain that {@code param} is equal to {@code value}. Be aware that the calculation may diverge in the case you set some values * that are not 'close' to approximate values. I the case of divergence you can set approximate * values using proper constructor * @param param parameter name - set one of AdvancedAffineBuilder static variables. * @param value required value */ public void setConstrain(String param, double value) { valueConstrain.put(param, value); } /** * Clears all constrains */ public void clearConstrains() { valueConstrain.clear(); } /** * Generates A matrix (Matrix of derivation of affine equation). Each column is derivation by * each transformation parameter (sx, sy, sxy, phi, tx,ty). The rows are derivations of fx and fy. * * @return A matrix */ protected GeneralMatrix getA() { GeneralMatrix A = new GeneralMatrix(2 * this.getMappedPositions().size(), 6); double cosphix = Math.cos(phix); double sinphix = Math.sin(phix); double cosphiy = Math.cos(phiy); double sinphiy = Math.sin(phiy); /** * Each row is calculated with values of proper GCPs */ for (int j = 0; j < (A.getNumRow() / 2); j++) { double x = getSourcePoints()[j].getOrdinate(0); double y = getSourcePoints()[j].getOrdinate(1); /************************* * * Derivation X * **************************/ double dxsx = cosphix*x; double dxsy = - sinphiy * y; double dxphix = -sx*sinphix* x; double dxphiy = -sy*cosphiy* y ; double dxtx = 1; double dxty = 0; /************************* * * Derivation Y * ***********************/ double dysx = sinphix * x; double dysy = cosphiy * y; double dyphix = sx*cosphix*x; double dyphiy = -sy*sinphiy* y ; double dytx = 0; double dyty = 1; A.setRow(j, new double[] { dxsx, dxsy, dxphix, dxphiy, dxtx, dxty }); A.setRow(A.getNumRow()/2 + j, new double[] { dysx, dysy, dyphix, dyphiy, dytx, dyty }); } return A; } /** * Fill L matrix. This matrix contains differences between expected value and value * calculated from affine parameters * @return l matrix */ protected GeneralMatrix getL() { GeneralMatrix l = new GeneralMatrix(2 * this.getMappedPositions().size(), 1); double cosphix = Math.cos(phix); double sinphix = Math.sin(phix); double cosphiy = Math.cos(phiy); double sinphiy = Math.sin(phiy); for (int j = 0; j < (l.getNumRow() / 2); j++) { double x = getSourcePoints()[j].getOrdinate(0); double y = getSourcePoints()[j].getOrdinate(1); /* a1 is target value - transfomed value*/ double dx = getTargetPoints()[j].getOrdinate(0) - (sx*cosphix*x - sy*sinphiy*y + tx); double dy = getTargetPoints()[j].getOrdinate(1) - (sx*sinphix*x + sy*cosphiy*y + ty); l.setElement(j, 0, dx); l.setElement((l.getNumRow() / 2) + j, 0, dy); } return l; } /** * Ask for dx matrix with default number of iterations and precision constrain. * @return dx matrix * @throws FactoryException */ private GeneralMatrix getDxMatrix() throws FactoryException { return getDxMatrix(dif, steps); } /** * Get matrix of affine coefficients as a result of LSM. * @param tolerance tolerance for iteration. * @param maxSteps max steps of iteration * @return dx matrix * @throws FactoryException */ private GeneralMatrix getDxMatrix(double tolerance, int maxSteps) throws FactoryException { /** * Matrix of new calculated coefficients */ GeneralMatrix xNew = new GeneralMatrix(6, 1); /** * Matrix of coefficients calculated in previous iteration */ GeneralMatrix xOld = new GeneralMatrix(6, 1); /** * Difference between each steps of iteration */ GeneralMatrix dxMatrix = new GeneralMatrix(6, 1); /** * Zero matrix */ GeneralMatrix zero = new GeneralMatrix(6, 1); zero.setZero(); /** * Result */ GeneralMatrix xk = new GeneralMatrix(6 + valueConstrain.size(), 1); // i is a number of iterations int i = 0; // iteration do { xOld.set(new double[] { sx, sy, phix, phiy, tx, ty }); GeneralMatrix A = getA(); GeneralMatrix l = getL(); GeneralMatrix AT = A.clone(); AT.transpose(); GeneralMatrix ATA = new GeneralMatrix(6, 6); GeneralMatrix ATl = new GeneralMatrix(6, 1); ATA.mul(AT, A); ATl.mul(AT, l); /**constrains**/ GeneralMatrix AB = createAB(ATA, getB()); AB.invert(); AB.negate(); GeneralMatrix AU = createAU(ATl, getU()); xk.mul(AB, AU); xk.copySubMatrix(0, 0, 6, xk.getNumCol(), 0, 0, dxMatrix); dxMatrix.negate(); // New values of x = dx + previous values xOld.negate(); xNew.sub(dxMatrix, xOld); // New values are setup for another iteration sx = xNew.getElement(0, 0); sy = xNew.getElement(1, 0); phix = xNew.getElement(2, 0); phiy = xNew.getElement(3, 0); tx = xNew.getElement(4, 0); ty = xNew.getElement(5, 0); i++; if (i > maxSteps) { //&& oldDxMatrix.getElement(0, 0) < dxMatrix.getElement(0, 0)){ throw new FactoryException("Calculation of transformation is divergating - try to set proper aproximate values"); } } while ((!dxMatrix.equals(zero, tolerance))); xNew.transpose(); return xNew; } @Override public int getMinimumPointCount() { return 3; } /** * Fill matrix of derivations of constrains by affine parameters. * @return B matrix */ protected GeneralMatrix getB() { GeneralMatrix B = new GeneralMatrix(valueConstrain.size(), 6); int i = 0; if (valueConstrain.containsKey(SX)) { B.setRow(i, new double[] { 1, 0, 0, 0, 0, 0 }); i++; } if (valueConstrain.containsKey(SY)) { B.setRow(i, new double[] { 0, 1, 0, 0, 0, 0 }); i++; } if (valueConstrain.containsKey(PHIX)) { B.setRow(i, new double[] { 0, 0, 1, 0, 0, 0 }); i++; } if (valueConstrain.containsKey(PHIY)) { B.setRow(i, new double[] { 0, 0, 0, 1, 0, 0 }); i++; } if (valueConstrain.containsKey(TX)) { B.setRow(i, new double[] { 0, 0, 0, 0, 1, 0 }); i++; } if (valueConstrain.containsKey(TY)) { B.setRow(i, new double[] { 0, 0, 0, 0, 0, 1 }); i++; } if (valueConstrain.containsKey(SXY)) { B.setRow(i, new double[] { 0, 0, -1, 1, 0, 0 }); i++; } return B; } /** * Fill matrix of constrain values (e.g. for constrain skew = 0 the value is 0) * @return U matrix */ protected GeneralMatrix getU() { GeneralMatrix U = new GeneralMatrix(valueConstrain.size(), 1); int i = 0; if (valueConstrain.containsKey(SX)) { U.setRow(i, new double[] { -sx + valueConstrain.get(SX) }); i++; } if (valueConstrain.containsKey(SY)) { U.setRow(i, new double[] { -sy + valueConstrain.get(SY) }); i++; } if (valueConstrain.containsKey(PHIX)) { U.setRow(i, new double[] { -phix + valueConstrain.get(PHIX)}); i++; } if (valueConstrain.containsKey(PHIY)) { U.setRow(i, new double[] { -phiy + valueConstrain.get(PHIY) }); i++; } if (valueConstrain.containsKey(TX)) { U.setRow(i, new double[] { -tx + valueConstrain.get(TX) }); i++; } if (valueConstrain.containsKey(SXY)) { U.setRow(i, new double[] { (phix-phiy) + valueConstrain.get(SXY) }); i++; } else if (valueConstrain.containsKey(TY)) { U.setRow(i, new double[] { -ty + valueConstrain.get(TY) }); i++; } return U; } /** * Joins A <sup>T</sup> matrix with L * @param ATl * @param U * @return matrix constructs from ATl and U */ private GeneralMatrix createAU(GeneralMatrix ATl, GeneralMatrix U) { GeneralMatrix AU = new GeneralMatrix(ATl.getNumRow() + U.getNumRow(), ATl.getNumCol()); ATl.copySubMatrix(0, 0, ATl.getNumRow(), ATl.getNumCol(), 0, 0, AU); U.copySubMatrix(0, 0, U.getNumRow(), U.getNumCol(), ATl.getNumRow(), 0, AU); return AU; } /** * Joins A matrix with B. * result is: * (A B ) * (B 0 ) * * @param ATA * @param B * @return matrix constructs from ATA and B */ private GeneralMatrix createAB(GeneralMatrix ATA, GeneralMatrix B) { GeneralMatrix BT = B.clone(); BT.transpose(); GeneralMatrix AAB = new GeneralMatrix(ATA.getNumRow() + B.getNumRow(), ATA.getNumCol() + BT.getNumCol()); ATA.copySubMatrix(0, 0, ATA.getNumRow(), ATA.getNumCol(), 0, 0, AAB); B.copySubMatrix(0, 0, B.getNumRow(), B.getNumCol(), ATA.getNumRow(), 0, AAB); BT.copySubMatrix(0, 0, BT.getNumRow(), BT.getNumCol(), 0, ATA.getNumCol(), AAB); GeneralMatrix zero = new GeneralMatrix(B.getNumRow(), B.getNumRow()); zero.setZero(); zero.copySubMatrix(0, 0, zero.getNumRow(), zero.getNumCol(), B.getNumCol(), B.getNumCol(), AAB); return AAB; } /** * Calculates coefficients of Projective transformation matrix from geometric parameters. * * @return Projective Matrix * @throws FactoryException */ protected GeneralMatrix getProjectiveMatrix() throws FactoryException { GeneralMatrix M = new GeneralMatrix(3, 3); /** * Runs calculation of parameter values */ double[] param = getDxMatrix().getElements()[0]; /** * calcuates matrix coefficients form geometric coefficients */ double a11 = sx * Math.cos(phix); double a12 = -sy * Math.sin(phiy); double a21 = sx* Math.sin(phix); double a22 = sy * Math.cos(phiy); /** * Fill the metrix */ double[] m0 = { a11, a12, param[4] }; double[] m1 = { a21, a22, param[5] }; double[] m2 = { 0, 0, 1 }; M.setRow(0, m0); M.setRow(1, m1); M.setRow(2, m2); return M; } @Override protected MathTransform computeMathTransform() throws FactoryException { if (valueConstrain.size() == 0) { return affineTrans; } return ProjectiveTransform.create(getProjectiveMatrix()); } /** * Returns difference that is required between steps in iteration * @return max difference that is required for iteration steps */ public double getMaxIterationDifference() { return dif; } /** * Sets difference that is required between steps in iteration. * @param dif */ public void setMaxIterationDifference(double dif) { this.dif = dif; } /** * Return max number of iteration steps. If the difference between calculated values * in each iteration steps is still bigger than required then Exception is thrown. * This is not the number that was really needed for iteration. * @return max number of iteration steps. */ public int getNumberOfIterationSteps() { return steps; } /** * Sets max number of iteration steps. If the difference between calculated values * in each iteration steps is still bigger than required than Exception is thrown. * @param steps max number of iterations. */ public void setNumberOfIterationSteps(int steps) { this.steps = steps; } }