/*
* 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.
*/
package org.geotools.referencing.operation.transform;
import java.io.Serializable;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.geotools.geometry.GeneralDirectPosition;
import org.geotools.referencing.operation.LinearTransform;
import org.geotools.referencing.operation.matrix.GeneralMatrix;
import org.geotools.referencing.wkt.Formatter;
import org.geotools.util.Utilities;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
/**
* Transform which passes through a subset of ordinates to another transform.
* This allows transforms to operate on a subset of ordinates. For example,
* if you have (latitude,longitude,height)
* coordinates, then you may wish to convert the height values from feet to
* meters without affecting the latitude and longitude values.
*
* @since 2.0
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see DimensionFilter
*/
public class PassThroughTransform extends AbstractMathTransform implements Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -1673997634240223449L;
/**
* Index of the first affected ordinate.
*/
protected final int firstAffectedOrdinate;
/**
* Number of unaffected ordinates after the affected ones.
* Always 0 when used through the strict OpenGIS API.
*/
protected final int numTrailingOrdinates;
/**
* The sub transform.
*
* @see #getSubTransform
*/
protected final MathTransform subTransform;
/**
* The inverse transform. This field will be computed only when needed.
* But it is serialized in order to avoid rounding error.
*/
private PassThroughTransform inverse;
/**
* Create a pass through transform.
*
* @param firstAffectedOrdinate Index of the first affected ordinate.
* @param subTransform The sub transform.
* @param numTrailingOrdinates Number of trailing ordinates to pass through.
* Affected ordinates will range from {@code firstAffectedOrdinate}
* inclusive to {@code dimTarget-numTrailingOrdinates} exclusive.
*/
protected PassThroughTransform(final int firstAffectedOrdinate,
final MathTransform subTransform,
final int numTrailingOrdinates)
{
if (firstAffectedOrdinate < 0) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
"firstAffectedOrdinate", firstAffectedOrdinate));
}
if (numTrailingOrdinates < 0) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
"numTrailingOrdinates", numTrailingOrdinates));
}
if (subTransform instanceof PassThroughTransform) {
final PassThroughTransform passThrough = (PassThroughTransform) subTransform;
this.firstAffectedOrdinate = passThrough.firstAffectedOrdinate + firstAffectedOrdinate;
this.numTrailingOrdinates = passThrough.numTrailingOrdinates + numTrailingOrdinates;
this.subTransform = passThrough.subTransform;
} else {
this.firstAffectedOrdinate = firstAffectedOrdinate;
this.numTrailingOrdinates = numTrailingOrdinates;
this.subTransform = subTransform;
}
}
/**
* Creates a transform which passes through a subset of ordinates to another transform.
* This allows transforms to operate on a subset of ordinates. For example, if you have
* (latitidue,longitude,height) coordinates, then you
* may wish to convert the height values from feet to meters without affecting the
* latitude and longitude values.
*
* @param firstAffectedOrdinate Index of the first affected ordinate.
* @param subTransform The sub transform.
* @param numTrailingOrdinates Number of trailing ordinates to pass through.
* Affected ordinates will range from {@code firstAffectedOrdinate}
* inclusive to {@code dimTarget-numTrailingOrdinates} exclusive.
* @return A pass through transform with the following dimensions:
*
* Source: firstAffectedOrdinate + subTransform.getSourceDimensions() + numTrailingOrdinates * Target: firstAffectedOrdinate + subTransform.getTargetDimensions() + numTrailingOrdinates*/ public static MathTransform create(final int firstAffectedOrdinate, final MathTransform subTransform, final int numTrailingOrdinates) { if (firstAffectedOrdinate < 0) { throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "firstAffectedOrdinate", firstAffectedOrdinate)); } if (numTrailingOrdinates < 0) { throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "numTrailingOrdinates", numTrailingOrdinates)); } if (firstAffectedOrdinate==0 && numTrailingOrdinates==0) { return subTransform; } /* * Optimize the "Identity transform" case. */ if (subTransform.isIdentity()) { final int dimension = subTransform.getSourceDimensions(); if (dimension == subTransform.getTargetDimensions()) { return IdentityTransform.create(firstAffectedOrdinate + dimension + numTrailingOrdinates); } } /* * Special case for transformation backed by a matrix. Is is possible to use a * new matrix for such transform, instead of wrapping the sub-transform into a * PassThroughTransform object. It is faster and easier to concatenate. */ if (subTransform instanceof LinearTransform) { GeneralMatrix matrix = toGMatrix(((LinearTransform)subTransform).getMatrix()); matrix = PassThroughTransform.expand(matrix, firstAffectedOrdinate, numTrailingOrdinates, 1); return ProjectiveTransform.create(matrix); } /* * Constructs the general PassThroughTransform object. An optimisation * for the "Pass through case" is done right in the constructor. */ return new PassThroughTransform(firstAffectedOrdinate, subTransform, numTrailingOrdinates); } /** * Returns the sub transform. * * @return The sub transform. * * @since 2.2 */ public MathTransform getSubTransform() { return subTransform; } /** * Ordered sequence of positive integers defining the positions in a coordinate * tuple of the coordinates affected by this pass-through transform. The returned * index are for source coordinates. * * @return The modified coordinates. */ public int[] getModifiedCoordinates() { final int[] index = new int[subTransform.getSourceDimensions()]; for (int i=0; i