/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/exse/ lat/lon GmbH http://www.lat-lon.de It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/) SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite Institut de Recherche pour le D�veloppement / US-Espace mailto:seasnet@teledetection.fr 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; either version 2.1 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: klaus.greve@uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.model.csct.ct; // Geometry import java.awt.geom.Point2D; import java.io.Serializable; import java.util.Arrays; import javax.media.jai.ParameterList; import javax.media.jai.PerspectiveTransform; import javax.media.jai.util.Range; import javax.vecmath.GMatrix; import javax.vecmath.SingularMatrixException; import org.deegree.model.csct.pt.CoordinatePoint; import org.deegree.model.csct.pt.Matrix; import org.deegree.model.csct.resources.css.ResourceKeys; import org.deegree.model.csct.resources.css.Resources; /** * Transforms multi-dimensional coordinate points using a {@link Matrix}. * * @version 1.00 * @author OpenGIS (www.opengis.org) * @author Martin Desruisseaux */ final class MatrixTransform extends AbstractMathTransform implements Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -2104496465933824935L; /** * the number of rows. */ private final int numRow; /** * the number of columns. */ private final int numCol; /** * Elements of the matrix. Column indice vary fastest. */ private final double[] elt; /** * Construct a transform. */ protected MatrixTransform(final GMatrix matrix) { numRow = matrix.getNumRow(); numCol = matrix.getNumCol(); elt = new double[numRow*numCol]; int index = 0; for (int j=0; j<numRow; j++) for (int i=0; i<numCol; i++) elt[index++] = matrix.getElement(j,i); } /** * Transforms an array of floating point coordinates by this matrix. Point coordinates * must have a dimension equals to <code>{@link Matrix#getNumCol}-1</code>. For example, * for square matrix of size 4×4, coordinate points are three-dimensional and * stored in the arrays starting at the specified offset (<code>srcOff</code>) in the order * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>, * x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>..., * x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>. * * The transformed points <code>(x',y',z')</code> are computed as below * (note that this computation is similar to {@link PerspectiveTransform}): * * <blockquote><pre> * [ u ] [ m<sub>00</sub> m<sub>01</sub> m<sub>02</sub> m<sub>03</sub> ] [ x ] * [ v ] = [ m<sub>10</sub> m<sub>11</sub> m<sub>12</sub> m<sub>13</sub> ] [ y ] * [ w ] [ m<sub>20</sub> m<sub>21</sub> m<sub>22</sub> m<sub>23</sub> ] [ z ] * [ t ] [ m<sub>30</sub> m<sub>31</sub> m<sub>32</sub> m<sub>33</sub> ] [ 1 ] * * x' = u/t * y' = v/t * y' = w/t * </pre></blockquote> * * @param srcPts The array containing the source point coordinates. * @param srcOff The offset to the first point to be transformed in the source array. * @param dstPts The array into which the transformed point coordinates are returned. * @param dstOff The offset to the location of the first transformed point that is stored * in the destination array. The source and destination array sections can * be overlaps. * @param numPts The number of points to be transformed */ public void transform(float[] srcPts, int srcOff, final float[] dstPts, int dstOff, int numPts) { final int inputDimension = numCol-1; // The last ordinate will be assumed equals to 1. final int outputDimension = numRow-1; final double[] buffer = new double[numRow]; if (srcPts==dstPts) { // We are going to write in the source array. Checks if // source and destination sections are going to clash. final int upperSrc = srcOff + numPts*inputDimension; if (upperSrc > dstOff) { if (inputDimension >= outputDimension ? dstOff > srcOff : dstOff + numPts*outputDimension > upperSrc) { // If source overlaps destination, then the easiest workaround is // to copy source data. This is not the most efficient however... srcPts = new float[numPts*inputDimension]; System.arraycopy(dstPts, srcOff, srcPts, 0, srcPts.length); srcOff = 0; } } } while (--numPts>=0) { int mix=0; for (int j=0; j<numRow; j++) { double sum=elt[mix + inputDimension]; for (int i=0; i<inputDimension; i++) { sum += srcPts[srcOff+i]*elt[mix++]; } buffer[j] = sum; mix++; } final double w = buffer[outputDimension]; for (int j=0; j<outputDimension; j++) { // 'w' is equals to 1 if the transform is affine. dstPts[dstOff++] = (float) (buffer[j]/w); } srcOff += inputDimension; } } /** * Transforms an array of floating point coordinates by this matrix. Point coordinates * must have a dimension equals to <code>{@link Matrix#getNumCol}-1</code>. For example, * for square matrix of size 4×4, coordinate points are three-dimensional and * stored in the arrays starting at the specified offset (<code>srcOff</code>) in the order * <code>[x<sub>0</sub>, y<sub>0</sub>, z<sub>0</sub>, * x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>..., * x<sub>n</sub>, y<sub>n</sub>, z<sub>n</sub>]</code>. * * The transformed points <code>(x',y',z')</code> are computed as below * (note that this computation is similar to {@link PerspectiveTransform}): * * <blockquote><pre> * [ u ] [ m<sub>00</sub> m<sub>01</sub> m<sub>02</sub> m<sub>03</sub> ] [ x ] * [ v ] = [ m<sub>10</sub> m<sub>11</sub> m<sub>12</sub> m<sub>13</sub> ] [ y ] * [ w ] [ m<sub>20</sub> m<sub>21</sub> m<sub>22</sub> m<sub>23</sub> ] [ z ] * [ t ] [ m<sub>30</sub> m<sub>31</sub> m<sub>32</sub> m<sub>33</sub> ] [ 1 ] * * x' = u/t * y' = v/t * y' = w/t * </pre></blockquote> * * @param srcPts The array containing the source point coordinates. * @param srcOff The offset to the first point to be transformed in the source array. * @param dstPts The array into which the transformed point coordinates are returned. * @param dstOff The offset to the location of the first transformed point that is stored * in the destination array. The source and destination array sections can * be overlaps. * @param numPts The number of points to be transformed */ public void transform(double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts) { final int inputDimension = numCol-1; // The last ordinate will be assumed equals to 1. final int outputDimension = numRow-1; final double[] buffer = new double[numRow]; if (srcPts==dstPts) { // We are going to write in the source array. Checks if // source and destination sections are going to clash. final int upperSrc = srcOff + numPts*inputDimension; if (upperSrc > dstOff) { if (inputDimension >= outputDimension ? dstOff > srcOff : dstOff + numPts*outputDimension > upperSrc) { // If source overlaps destination, then the easiest workaround is // to copy source data. This is not the most efficient however... srcPts = new double[numPts*inputDimension]; System.arraycopy(dstPts, srcOff, srcPts, 0, srcPts.length); srcOff = 0; } } } while (--numPts>=0) { int mix=0; for (int j=0; j<numRow; j++) { double sum=elt[mix + inputDimension]; for (int i=0; i<inputDimension; i++) { sum += srcPts[srcOff+i]*elt[mix++]; } buffer[j] = sum; mix++; } final double w = buffer[outputDimension]; for (int j=0; j<outputDimension; j++) { // 'w' is equals to 1 if the transform is affine. dstPts[dstOff++] = buffer[j]/w; } srcOff += inputDimension; } } /** * Gets the derivative of this transform at a point. * For a matrix transform, the derivative is the * same everywhere. */ public Matrix derivative(final Point2D point) {return derivative((CoordinatePoint)null);} /** * Gets the derivative of this transform at a point. * For a matrix transform, the derivative is the * same everywhere. */ public Matrix derivative(final CoordinatePoint point) { final Matrix matrix = getMatrix(); matrix.setSize(numRow-1, numCol-1); return matrix; } /** * Returns a copy of the matrix. */ public Matrix getMatrix() {return new Matrix(numRow, numCol, elt);} /** * Gets the dimension of input points. */ public int getDimSource() {return numCol-1;} /** * Gets the dimension of output points. */ public int getDimTarget() {return numRow-1;} /** * Tests whether this transform does not move any points. */ public boolean isIdentity() { if (numRow != numCol) return false; int index=0; for (int j=0; j<numRow; j++) for (int i=0; i<numCol; i++) if (elt[index++] != (i==j ? 1 : 0)) return false; return true; } /** * Creates the inverse transform of this object. */ public MathTransform inverse() throws NoninvertibleTransformException { if (isIdentity()) return this; final Matrix matrix = getMatrix(); try { matrix.invert(); } catch (SingularMatrixException exception) { NoninvertibleTransformException e = new NoninvertibleTransformException(Resources.format(ResourceKeys.ERROR_NONINVERTIBLE_TRANSFORM)); throw e; } return new MatrixTransform(matrix); } /** * Returns a hash value for this transform. * This value need not remain consistent between * different implementations of the same class. */ public int hashCode() { long code=2563217; for (int i=elt.length; --i>=0;) { code = code*37 + Double.doubleToLongBits(elt[i]); } return (int)(code >>> 32) ^ (int)code; } /** * Compares the specified object with * this math transform for equality. */ public boolean equals(final Object object) { if (object==this) return true; // Slight optimization if (super.equals(object)) { final MatrixTransform that = (MatrixTransform) object; return this.numRow == that.numRow && this.numCol == that.numCol && Arrays.equals(this.elt, that.elt); } return false; } /** * Returns the WKT for this math transform. */ public String toString() {return toString(getMatrix());} /** * Returns the WKT for an affine transform * using the specified matrix. */ static String toString(final Matrix matrix) { final int numRow = matrix.getNumRow(); final int numCol = matrix.getNumCol(); final StringBuffer buffer = paramMT("Affine"); final StringBuffer eltBuf = new StringBuffer("elt_"); addParameter(buffer, "Num_row", numRow); addParameter(buffer, "Num_col", numCol); for (int j=0; j<numRow; j++) { for (int i=0; i<numCol; i++) { final double value = matrix.getElement(j,i); if (value != (i==j ? 1 : 0)) { eltBuf.setLength(4); eltBuf.append(j); eltBuf.append('_'); eltBuf.append(i); addParameter(buffer, eltBuf.toString(), value); } } } buffer.append(']'); return buffer.toString(); } /** * The provider for {@link MatrixTransform}. * * @version 1.0 * @author Martin Desruisseaux */ static final class Provider extends MathTransformProvider { /** * Range of positives values. Range * goes from 1 to the maximum value. */ private static final Range POSITIVE_RANGE = new Range(Integer.class, new Integer(1), new Integer(Integer.MAX_VALUE)); /** * Create a provider for affine transforms of the specified * dimension. Created affine transforms will have a size of * <code>numRow × numCol</code>. * * @param numRow The number of matrix's rows. * @param numCol The number of matrix's columns. */ public Provider(final int numRow, final int numCol) { super("Affine", ResourceKeys.AFFINE_TRANSFORM, null); putInt("Num_row", numRow, POSITIVE_RANGE); // Add integer (not double) parameter putInt("Num_col", numCol, POSITIVE_RANGE); // Add integer (not double) parameter final StringBuffer buffer=new StringBuffer("elt_"); for (int j=0; j<=numRow; j++) { for (int i=0; i<=numCol; i++) { buffer.setLength(4); buffer.append(j); buffer.append('_'); buffer.append(i); put(buffer.toString(), (i==j) ? 1.0 : 0.0, null); } } } /** * Returns a transform for the specified parameters. * * @param parameters The parameter values in standard units. * @return A {@link MathTransform} object of this classification. */ public MathTransform create(final ParameterList parameters) {return staticCreate(parameters);} /** * Static version of {@link #create}, for use by * {@link MathTransformFactory#createParameterizedTransform}. */ public static MathTransform staticCreate(final ParameterList parameters) { final int numRow = parameters.getIntParameter("Num_row"); final int numCol = parameters.getIntParameter("Num_col"); final Matrix matrix = new Matrix(numRow, numCol); final String[] names = parameters.getParameterListDescriptor().getParamNames(); if (names!=null) { for (int i=0; i<names.length; i++) { final String name = names[i]; if (name.regionMatches(true, 0, "elt_", 0, 4)) { final int separator = name.lastIndexOf('_'); final int row = Integer.parseInt(name.substring(4, separator)); final int col = Integer.parseInt(name.substring(separator+1)); matrix.setElement(row, col, parameters.getDoubleParameter(name)); } } } if (numRow==3 && matrix.isAffine()) { return new AffineTransform2D(matrix.toAffineTransform2D()); } return new MatrixTransform(matrix); } } }