/* * 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.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.MathTransform1D; 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.geotools.referencing.operation.matrix.Matrix1; import org.geotools.referencing.operation.matrix.Matrix2; import org.geotools.referencing.operation.LinearTransform; /** * A one dimensional, linear transform. Input values <var>x</var> are converted into * output values <var>y</var> using the following equation: * * <p align="center"><var>y</var>  =  * {@linkplain #offset} + {@linkplain #scale}×<var>x</var></p> * * This class is the same as a 2×2 affine transform. However, this specialized * {@code LinearTransform1D} class is faster. It is defined there because extensively * used by {@link org.geotools.coverage.grid.GridCoverage2D}. * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see LogarithmicTransform1D * @see ExponentialTransform1D */ public class LinearTransform1D extends AbstractMathTransform implements MathTransform1D, LinearTransform, Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -7595037195668813000L; /** * The identity transform. */ public static final LinearTransform1D IDENTITY = IdentityTransform1D.ONE; /** * The value which is multiplied to input values. */ public final double scale; /** * The value to add to input values. */ public final double offset; /** * The inverse of this transform. Created only when first needed. */ private transient MathTransform1D inverse; /** * Constructs a new linear transform. This constructor is provided for subclasses only. * Instances should be created using the {@linkplain #create factory method}, which * may returns optimized implementations for some particular argument values. * * @param scale The {@code scale} term in the linear equation. * @param offset The {@code offset} term in the linear equation. */ protected LinearTransform1D(final double scale, final double offset) { this.scale = scale; this.offset = offset; } /** * Constructs a new linear transform. * * @param scale The {@code scale} term in the linear equation. * @param offset The {@code offset} term in the linear equation. */ public static LinearTransform1D create(final double scale, final double offset) { if (scale == 0) { return new ConstantTransform1D(offset); } if (scale==1 && offset==0) { return IDENTITY; } return new LinearTransform1D(scale, offset); } /** * Returns the parameter descriptors for this math transform. */ @Override public ParameterDescriptorGroup getParameterDescriptors() { return ProjectiveTransform.ProviderAffine.PARAMETERS; } /** * Returns the matrix elements as a group of parameters values. The number of parameters * depends on the matrix size. Only matrix elements different from their default value * will be included in this group. * * @return A copy of the parameter values for this math transform. */ @Override public ParameterValueGroup getParameterValues() { return ProjectiveTransform.getParameterValues(getMatrix()); } /** * Gets the dimension of input points, which is 1. */ public int getSourceDimensions() { return 1; } /** * Gets the dimension of output points, which is 1. */ public int getTargetDimensions() { return 1; } /** * Returns this transform as an affine transform matrix. */ public Matrix getMatrix() { return new Matrix2(scale, offset, 0, 1); } /** * Creates the inverse transform of this object. */ @Override public MathTransform1D inverse() throws NoninvertibleTransformException { if (inverse == null) { if (isIdentity()) { inverse = this; } else if (scale != 0) { final LinearTransform1D inverse; inverse = create(1/scale, -offset/scale); inverse.inverse = this; this.inverse = inverse; } else { inverse = (MathTransform1D) super.inverse(); } } return inverse; } /** * Tests whether this transform does not move any points. */ @Override public boolean isIdentity() { return isIdentity(0); } /** * Tests whether this transform does not move any points by using the provided tolerance. * This method work in the same way than * {@link org.geotools.referencing.operation.matrix.XMatrix#isIdentity(double)}. * * @since 2.3.1 */ public boolean isIdentity(double tolerance) { tolerance = Math.abs(tolerance); return Math.abs(offset) <= tolerance && Math.abs(scale-1) <= tolerance; } /** * Gets the derivative of this transform at a point. This implementation is different * from the default {@link AbstractMathTransform#derivative} implementation in that no * coordinate point is required and {@link Double#NaN} may be a legal output value for * some users. */ @Override public Matrix derivative(final DirectPosition point) throws TransformException { return new Matrix1(scale); } /** * Gets the derivative of this function at a value. */ public double derivative(final double value) { return scale; } /** * Transforms the specified value. */ public double transform(double value) { return offset + scale*value; } /** * Transforms a list of coordinate point ordinal values. */ @Override public void transform(final float[] srcPts, int srcOff, final float[] dstPts, int dstOff, int numPts) { if (srcPts!=dstPts || srcOff>=dstOff) { while (--numPts >= 0) { dstPts[dstOff++] = (float) (offset + scale*srcPts[srcOff++]); } } else { srcOff += numPts; dstOff += numPts; while (--numPts >= 0) { dstPts[--dstOff] = (float) (offset + scale*srcPts[--srcOff]); } } } /** * Transforms a list of coordinate point ordinal values. */ public void transform(final double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts) { if (srcPts!=dstPts || srcOff>=dstOff) { while (--numPts >= 0) { dstPts[dstOff++] = offset + scale*srcPts[srcOff++]; } } else { srcOff += numPts; dstOff += numPts; while (--numPts >= 0) { dstPts[--dstOff] = offset + scale*srcPts[--srcOff]; } } } /** * Returns a hash value for this transform. * This value need not remain consistent between * different implementations of the same class. */ @Override public int hashCode() { long code; code = (int)serialVersionUID + Double.doubleToRawLongBits(offset); code = code*37 + Double.doubleToRawLongBits(scale); 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 LinearTransform1D that = (LinearTransform1D) object; return Double.doubleToRawLongBits(this.scale) == Double.doubleToRawLongBits(that.scale) && Double.doubleToRawLongBits(this.offset) == Double.doubleToRawLongBits(that.offset); /* * NOTE: 'LinearTransform1D' and 'ConstantTransform1D' are heavily used by 'Category' * from 'org.geotools.cv' package. It is essential for Cateory to differenciate * various NaN values. Because 'equals' is used by CanonicalSet.unique(Object) * (which is used by 'DefaultMathTransformFactory'), test for equality can't use * the doubleToLongBits method because it collapse all NaN into a single canonical * value. The 'doubleToRawLongBits' instead provided the needed functionality. */ } return false; } }