/* * 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.util.Arrays; import org.opengis.referencing.FactoryException; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransformFactory; import org.geotools.factory.Hints; import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.operation.LinearTransform; import org.geotools.referencing.operation.matrix.XMatrix; import org.geotools.referencing.operation.matrix.MatrixFactory; import org.geotools.referencing.operation.matrix.GeneralMatrix; import org.geotools.resources.XArray; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * An utility class for the separation of {@linkplain ConcatenatedTransform concatenation} of * {@linkplain PassThroughTransform pass through transforms}. Given an arbitrary * {@linkplain MathTransform math transform}, this utility class will returns a new math transform * that operates only of a given set of source dimensions. For example if the supplied * {@code transform} has (x, y, z) inputs and * (longitude, latitude, height) outputs, then * the following code: * *
* ** {@linkplain #addSourceDimensionRange addSourceDimensionRange}(0, 2); * MathTransform mt = {@linkplain #separate separate}(transform); *
will returns a transform with (x, y) inputs and (probably) * (longitude, latitude) outputs. The later can be verified with * a call to {@link #getTargetDimensions}.
* * @since 2.1 * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * @author Simone Giannecchini * * @todo This class is specific to Geotools implementation; it is better to avoid it if * you can. It could be generalized a bit if we perform the same operations on * {@link org.opengis.referencing.operation.CoordinateOperation} interfaces instead * of math transforms. We should revisit this issue after grid coverage API has been * revisited (since grid coverage is a user of this class). * * @todo This class contains a set of static methods that could be factored out in * some kind of {@code org.geotools.util.SortedIntegerSet} implementation. */ public class DimensionFilter { /** * Hint key for specifying a particular instance of {@code DimensionFilter} to use. * * @see #getInstance * * @since 2.5 */ public static final Hints.Key INSTANCE = new Hints.Key(DimensionFilter.class); /** * The input dimensions to keep, in strictly increasing order. * This sequence can contains any integers in the range 0 inclusive to *transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}
* exclusive.
*/
private int[] sourceDimensions;
/**
* The output dimensions to keep, in strictly increasing order.
* This sequence can contains any integers in the range 0 inclusive to
* transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}
* exclusive.
*/
private int[] targetDimensions;
/**
* The factory for the creation of new math transforms.
*/
private final MathTransformFactory factory;
/**
* Constructs a dimension filter with the
* {@linkplain ReferencingFactoryFinder#getMathTransformFactory default math transform factory}.
*/
public DimensionFilter() {
this(ReferencingFactoryFinder.getMathTransformFactory(null));
}
/**
* Constructs a dimension filter with a
* {@linkplain ReferencingFactoryFinder#getMathTransformFactory math transform factory
* built using the provided hints}.
*
* @param hints Hints to control the creation of the {@link MathTransformFactory}.
*/
public DimensionFilter(final Hints hints) {
this(ReferencingFactoryFinder.getMathTransformFactory(hints));
}
/**
* Constructs a dimension filter with the specified factory.
*
* @param factory The factory for the creation of new math transforms.
*/
public DimensionFilter(final MathTransformFactory factory) {
this.factory = factory;
}
/**
* Creates or returns an existing instance from the given set of hints. If the hints contain
* a value for the {@link #INSTANCE} key, this value is returned. Otherwise a new instance is
* {@linkplain #DimensionFilter(Hints) created} with the given hints.
*
* @param hints The hints, or {@code null} if none.
* @return An existing or a new {@code DimensionFilter} instance (never {@code null}).
*
* @see #INSTANCE
*
* @since 2.5
*/
public static DimensionFilter getInstance(final Hints hints) {
if (hints != null) {
final DimensionFilter candidate = (DimensionFilter) hints.get(INSTANCE);
if (candidate != null) {
candidate.clear();
return candidate;
}
}
return new DimensionFilter(hints);
}
/**
* Clears any {@linkplain #getSourceDimensions source} and
* {@linkplain #getTargetDimensions target dimension} setting.
*/
public void clear() {
sourceDimensions = null;
targetDimensions = null;
}
/**
* Add an input dimension to keep. The {@code dimension} applies to the
* source dimensions of the transform to be given to
* {@linkplain #separate separate}(transform)
.
* The number must be in the range 0 inclusive to
* transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}
* exclusive.
*
* @param dimension The dimension to add.
* @throws IllegalArgumentException if {@code dimension} is negative.
*/
public void addSourceDimension(final int dimension) throws IllegalArgumentException {
sourceDimensions = add(sourceDimensions, dimension);
}
/**
* Add input dimensions to keep. The {@code dimensions} apply to the
* source dimensions of the transform to be given to
* {@linkplain #separate separate}(transform)
.
* All numbers must be in the range 0 inclusive to
* transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}
* exclusive. The {@code dimensions} values must be in strictly increasing order.
*
* @param dimensions The new sequence of dimensions.
* @throws IllegalArgumentException if {@code dimensions} contains negative values or
* is not a strictly increasing sequence.
*/
public void addSourceDimensions(final int[] dimensions) throws IllegalArgumentException {
sourceDimensions = add(sourceDimensions, dimensions);
}
/**
* Add a range of input dimensions to keep. The {@code lower} and {@code upper} values
* apply to the source dimensions of the transform to be given to
* {@linkplain #separate separate}(transform)
.
*
* @param lower The lower dimension, inclusive. Must not be smaller than 0.
* @param upper The upper dimension, exclusive. Must not be greater than
* transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}
.
*/
public void addSourceDimensionRange(final int lower, final int upper)
throws IllegalArgumentException
{
sourceDimensions = add(sourceDimensions, lower, upper);
}
/**
* Returns the input dimensions. This information is available only if at least one
* setter method has been explicitly invoked for source dimensions.
*
* @return The input dimension as a sequence of strictly increasing values.
* @throws IllegalStateException if input dimensions have not been set.
*/
public int[] getSourceDimensions() throws IllegalStateException {
if (sourceDimensions != null) {
return sourceDimensions.clone();
}
throw new IllegalStateException();
}
/**
* Add an output dimension to keep. The {@code dimension} applies to the
* target dimensions of the transform to be given to
* {@linkplain #separate separate}(transform)
.
* The number must be in the range 0 inclusive to
* transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}
* exclusive.
*
* @param dimension The dimension to add.
* @throws IllegalArgumentException if {@code dimension} is negative.
*/
public void addTargetDimension(final int dimension) throws IllegalArgumentException {
targetDimensions = add(targetDimensions, dimension);
}
/**
* Add output dimensions to keep. The {@code dimensions} apply to the
* target dimensions of the transform to be given to
* {@linkplain #separate separate}(transform)
.
* All numbers must be in the range 0 inclusive to
* transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}
* exclusive. The {@code dimensions} values must be in strictly increasing order.
*
* @param dimensions The new sequence of dimensions.
* @throws IllegalArgumentException if {@code dimensions} contains negative values or
* is not a strictly increasing sequence.
*/
public void addTargetDimensions(int[] dimensions) throws IllegalArgumentException {
targetDimensions = add(targetDimensions, dimensions);
}
/**
* Add a range of output dimensions to keep. The {@code lower} and {@code upper} values
* apply to the target dimensions of the transform to be given to
* {@linkplain #separate separate}(transform)
.
*
* @param lower The lower dimension, inclusive. Must not be smaller than 0.
* @param upper The upper dimension, exclusive. Must not be greater than
* transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}
.
*/
public void addTargetDimensionRange(final int lower, final int upper)
throws IllegalArgumentException
{
targetDimensions = add(targetDimensions, lower, upper);
}
/**
* Returns the output dimensions. This information is available only if one of the following
* conditions is meet:
* *
{@linkplain #separate separate}(transform)
* has been invoked at least once, in which case the target dimensions are inferred
* automatically from the {@linkplain #getSourceDimensions source dimensions} and the
* {@code transform}.*
If {@linkplain #getSourceDimensions source dimensions} are unspecified, then the * returned transform will expects all source dimensions as input but will produces only * the specified {@linkplain #getTargetDimensions target dimensions} as output.
If {@linkplain #getTargetDimensions target dimensions} are unspecified, then the * returned transform will expects only the specified {@linkplain #getSourceDimensions * source dimensions} as input, and the target dimensions will be inferred * automatically.
* This transform may be see as a non-square matrix transform with less rows
* than columns, concatenated with {@code transform}. However, invoking
* {@code createFilterTransfom(...)} allows the optimization of some common cases.
*
* @param transform The transform to reduces.
* @return The {@code transform} keeping only the output dimensions.
* @throws FactoryException if the transform can't be created.
*/
private MathTransform separateOutput(MathTransform transform) throws FactoryException {
final int dimSource = transform.getSourceDimensions();
final int dimTarget = transform.getTargetDimensions();
final int dimOutput = targetDimensions.length;
final int lower = targetDimensions[0];
final int upper = targetDimensions[dimOutput-1];
assert XArray.isStrictlySorted(targetDimensions);
if (upper > dimTarget) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "targetDimensions", upper));
}
if (dimOutput == dimTarget) {
assert lower==0 && upper==dimTarget;
return transform;
}
/*
* If the transform is an instance of "pass through" transform but no dimension from its
* subtransform is requested, then ignore the subtransform (i.e. treat the whole transform
* as identity, except for the number of output dimension which may be different from the
* number of input dimension).
*/
int dimPass = 0;
int dimDiff = 0;
int dimStep = dimTarget;
if (transform instanceof PassThroughTransform) {
final PassThroughTransform passThrough = (PassThroughTransform) transform;
final int subLower = passThrough.firstAffectedOrdinate;
final int subUpper = subLower + passThrough.subTransform.getTargetDimensions();
if (!containsAny(targetDimensions, subLower, subUpper)) {
transform = null;
dimStep = dimSource;
dimPass = subLower;
dimDiff = (subLower + passThrough.subTransform.getSourceDimensions()) - subUpper;
}
}
/*
* Creates the matrix to be used as a filter, [x'] [1 0 0 0] [x]
* and concatenates it to the transform. The [z'] = [0 0 1 0] [y]
* matrix will contains only a 1 for the output [1 ] [0 0 0 1] [z]
* dimension to keep, as in the following example: [1]
*/
final XMatrix matrix = MatrixFactory.create(dimOutput+1, dimStep+1);
matrix.setZero();
for (int j=0; j