/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-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. * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.referencing.operation.transform; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.vecmath.MismatchedSizeException; import javax.vecmath.SingularMatrixException; import javax.measure.unit.NonSI; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.geometry.DirectPosition; import org.geotools.metadata.iso.citation.Citations; import org.geotools.parameter.MatrixParameterDescriptors; import org.geotools.parameter.MatrixParameters; import org.geotools.referencing.NamedIdentifier; import org.geotools.referencing.operation.LinearTransform; import org.geotools.referencing.operation.MathTransformProvider; import org.geotools.referencing.operation.matrix.MatrixFactory; import org.geotools.referencing.operation.matrix.GeneralMatrix; import org.geotools.referencing.operation.matrix.XMatrix; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; /** * A usually affine, or otherwise a projective transform. A projective transform is capable of * mapping an arbitrary quadrilateral into another arbitrary quadrilateral, while preserving the * straightness of lines. In the special case where the transform is affine, the parallelism of * lines in the source is preserved in the output. *

* Such a coordinate transformation can be represented by a square {@linkplain GeneralMatrix matrix} * of an arbitrary size. Point coordinates must have a dimension equals to * {@linkplain Matrix#getNumCol}-1. For example, for square matrix of size 4×4, * coordinate points are three-dimensional. The transformed points (x',y',z') are * computed as below (note that this computation is similar to * {@link javax.media.jai.PerspectiveTransform} in Java Advanced Imaging): * *

 * [ u ]     [ m00  m01  m02  m03 ] [ x ]
 * [ v ]  =  [ m10  m11  m12  m13 ] [ y ]
 * [ w ]     [ m20  m21  m22  m23 ] [ z ]
 * [ t ]     [ m30  m31  m32  m33 ] [ 1 ]
 *
 *   x' = u/t
 *   y' = v/t
 *   y' = w/t
 * 
* * In the special case of an affine transform, the last row contains only zero * values except in the last column, which contains 1. * * @since 2.0 * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see javax.media.jai.PerspectiveTransform * @see java.awt.geom.AffineTransform * @see Affine transformation on MathWorld */ public class ProjectiveTransform extends AbstractMathTransform implements LinearTransform, 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; /** * The inverse transform. Will be created only when first needed. */ private transient ProjectiveTransform inverse; /** * Constructs a transform from the specified matrix. * The matrix should be affine, but it will not be verified. * * @param matrix The matrix. */ protected ProjectiveTransform(final Matrix matrix) { numRow = matrix.getNumRow(); numCol = matrix.getNumCol(); elt = new double[numRow*numCol]; int index = 0; for (int j=0; jJava2D. * * @param matrix The affine transform as a matrix. * @return The transform for the given matrix. */ public static LinearTransform create(final AffineTransform matrix) { if (matrix.isIdentity()) { return IdentityTransform.create(2); } return new AffineTransform2D(matrix); } /** * Creates a transform that apply a uniform scale along all axis. * * @param dimension The input and output dimensions. * @param scale The scale factor. * @return The scale transform. * * @since 2.3 */ public static LinearTransform createScale(final int dimension, final double scale) { if (scale == 1) { return IdentityTransform.create(dimension); } final Matrix matrix = new GeneralMatrix(dimension + 1); for (int i=0; i{@link Matrix#getNumCol}-1. 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}) in the order * [x0, y0, z0, * x1, y1, z1..., * xn, yn, zn]. * * @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 */ @Override 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{@link Matrix#getNumCol}-1. 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}) in the order * [x0, y0, z0, * x1, y1, z1..., * xn, yn, zn]. * * @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=0;) { code = code*37 + Double.doubleToLongBits(elt[i]); } return (int)(code >>> 32) ^ (int)code; } /** * Compares the specified object with * this math transform for equality. */ @Override public boolean equals(final Object object) { if (object == this) { // Slight optimization return true; } if (super.equals(object)) { final ProjectiveTransform that = (ProjectiveTransform) object; return this.numRow == that.numRow && this.numCol == that.numCol && Arrays.equals(this.elt, that.elt); } return false; } /** * The provider for the "Affine general parametric transformation" (EPSG 9624). * The OGC's name is {@code "Affine"}. The default matrix size is * {@value org.geotools.parameter.MatrixParameterDescriptors#DEFAULT_MATRIX_SIZE}×{@value * org.geotools.parameter.MatrixParameterDescriptors#DEFAULT_MATRIX_SIZE}. *

* Note that affine transform is a special case of projective transform. * * @version $Id$ * @author Martin Desruisseaux (IRD) */ public static final class ProviderAffine extends MathTransformProvider { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 649555815622129472L; /** * The set of predefined providers. */ private static final ProviderAffine[] methods = new ProviderAffine[8]; /** * The parameters group. * * @todo We should register EPSG parameter identifiers (A0, A1, A2, B0, B1, B2) as well. */ static final ParameterDescriptorGroup PARAMETERS; static { final NamedIdentifier name = new NamedIdentifier(Citations.OGC, "Affine"); final Map properties = new HashMap(4, 0.8f); properties.put(NAME_KEY, name); properties.put(IDENTIFIERS_KEY, name); properties.put(ALIAS_KEY, new NamedIdentifier[] {name, new NamedIdentifier(Citations.EPSG, "Affine general parametric transformation"), new NamedIdentifier(Citations.EPSG, "9624"), new NamedIdentifier(Citations.GEOTOOLS, Vocabulary.formatInternational(VocabularyKeys.AFFINE_TRANSFORM)) }); PARAMETERS = new MatrixParameterDescriptors(properties); } /** * Creates a provider for affine transform with a default matrix size. */ public ProviderAffine() { this(MatrixParameterDescriptors.DEFAULT_MATRIX_SIZE-1, MatrixParameterDescriptors.DEFAULT_MATRIX_SIZE-1); methods[MatrixParameterDescriptors.DEFAULT_MATRIX_SIZE-2] = this; } /** * Creates a provider for affine transform with the specified dimensions. */ private ProviderAffine(final int sourceDimensions, final int targetDimensions) { super(sourceDimensions, targetDimensions, PARAMETERS); } /** * Returns the operation type. */ @Override public Class getOperationType() { return Conversion.class; } /** * Creates a projective transform from the specified group of parameter values. * * @param values The group of parameter values. * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected MathTransform createMathTransform(final ParameterValueGroup values) throws ParameterNotFoundException { final MathTransform transform; transform = create(((MatrixParameterDescriptors) getParameters()).getMatrix(values)); return new Delegate(transform, getProvider(transform.getSourceDimensions(), transform.getTargetDimensions())); } /** * Returns the operation method for the specified source and target dimensions. * This method provides different methods for different matrix sizes. * * @param sourceDimensions The number of source dimensions. * @param targetDimensions The number of target dimensions. * @return The provider for transforms of the given source and target dimensions. */ public static ProviderAffine getProvider(final int sourceDimensions, final int targetDimensions) { if (sourceDimensions == targetDimensions) { final int i = sourceDimensions - 1; if (i>=0 && iLongitude rotation" (EPSG 9601). * * @version $Id$ * @author Martin Desruisseaux (IRD) */ public static final class ProviderLongitudeRotation extends MathTransformProvider { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -2104496465933824935L; /** * The longitude offset. */ public static final ParameterDescriptor OFFSET = createDescriptor( new NamedIdentifier[] { new NamedIdentifier(Citations.EPSG, "Longitude offset") }, Double.NaN, -180, +180, NonSI.DEGREE_ANGLE); /** * The parameters group. */ static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { new NamedIdentifier(Citations.EPSG, "Longitude rotation"), new NamedIdentifier(Citations.EPSG, "9601") }, new ParameterDescriptor[] { OFFSET }); /** * Constructs a provider with default parameters. */ public ProviderLongitudeRotation() { super(2, 2, PARAMETERS); } /** * Returns the operation type. */ @Override public Class getOperationType() { return Conversion.class; } /** * Creates a transform from the specified group of parameter values. * * @param values The group of parameter values. * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected MathTransform createMathTransform(final ParameterValueGroup values) throws ParameterNotFoundException { final double offset = doubleValue(OFFSET, values); return create(AffineTransform.getTranslateInstance(offset, 0)); } } }