/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-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 javax.measure.unit.Unit; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform1D; import org.geotools.metadata.iso.citation.Citations; import org.geotools.parameter.DefaultParameterDescriptor; import org.geotools.parameter.FloatParameter; import org.geotools.referencing.NamedIdentifier; import org.geotools.referencing.operation.LinearTransform; import org.geotools.referencing.operation.MathTransformProvider; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.resources.i18n.Vocabulary; /** * A one dimensional, logarithmic 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} + log<sub>{@linkplain #base}</sub>(<var>x</var>) *   =   * {@linkplain #offset} + ln(<var>x</var>)/ln({@linkplain #base})</p> * * This transform is the inverse of {@link ExponentialTransform1D}. * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see ExponentialTransform1D * @see LinearTransform1D */ public class LogarithmicTransform1D extends AbstractMathTransform implements MathTransform1D, Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 1535101265352133948L; /** * Tolerance value for floating point comparaison. */ private static final double EPS = 1E-8; /** * The base of the logarithm. */ public final double base; /** * Natural logarithm of {@link #base}. */ final double lnBase; /** * The offset to add to the logarithm. */ public final double offset; /** * The inverse of this transform. Created only when first needed. * Serialized in order to avoid rounding error if this transform * is actually the one which was created from the inverse. */ private MathTransform1D inverse; /** * Constructs a new logarithmic transform which is the * inverse of the supplied exponentional transform. */ private LogarithmicTransform1D(final ExponentialTransform1D inverse) { this.base = inverse.base; this.lnBase = inverse.lnBase; this.offset = -Math.log(inverse.scale) / lnBase; this.inverse = inverse; } /** * Constructs a new logarithmic 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 base The base of the logarithm (typically 10). * @param offset The offset to add to the logarithm. */ protected LogarithmicTransform1D(final double base, final double offset) { this.base = base; this.offset = offset; this.lnBase = Math.log(base); } /** * Constructs a new logarithmic transform which is the * inverse of the supplied exponentional transform. */ static LogarithmicTransform1D create(final ExponentialTransform1D inverse) { if (Math.abs(inverse.base - 10) < EPS) { return new Base10(inverse); } return new LogarithmicTransform1D(inverse); } /** * Constructs a new logarithmic transform. * * @param base The base of the logarithm (typically 10). * @param offset The offset to add to the logarithm. * @return The math transform. */ public static MathTransform1D create(final double base, final double offset) { if (base == Double.POSITIVE_INFINITY || Math.abs(base) <= EPS) { return LinearTransform1D.create(0, offset); } if (Math.abs(base - 10) < EPS) { return new Base10(offset); } return new LogarithmicTransform1D(base, offset); } /** * Returns the parameter descriptors for this math transform. */ @Override public ParameterDescriptorGroup getParameterDescriptors() { return Provider.PARAMETERS; } /** * Returns the parameter values for this math transform. * * @return A copy of the parameter values for this math transform. */ @Override public ParameterValueGroup getParameterValues() { return new org.geotools.parameter.ParameterGroup(getParameterDescriptors(), new ParameterValue[] { new FloatParameter(Provider.BASE, base), new FloatParameter(Provider.OFFSET, offset)}); } /** * 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; } /** * Creates the inverse transform of this object. */ @Override public MathTransform1D inverse() { if (inverse == null) { inverse = new ExponentialTransform1D(this); } return inverse; } /** * Gets the derivative of this function at a value. */ public double derivative(final double value) { return 1 / (lnBase * value); } /** * Transforms the specified value. */ public double transform(final double value) { return Math.log(value)/lnBase + offset; } /** * 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) (Math.log(srcPts[srcOff++])/lnBase + offset); } } else { srcOff += numPts; dstOff += numPts; while (--numPts >= 0) { dstPts[--dstOff] = (float) (Math.log(srcPts[srcOff++])/lnBase + offset); } } } /** * 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++] = Math.log(srcPts[srcOff++])/lnBase + offset; } } else { srcOff += numPts; dstOff += numPts; while (--numPts >= 0) { dstPts[--dstOff] = Math.log(srcPts[srcOff++])/lnBase + offset; } } } /** * Special case for base 10 taking advantage of extra precision provided by {@link Math#log10}. */ private static final class Base10 extends LogarithmicTransform1D { /** For cross-version compatibility. */ private static final long serialVersionUID = -5435804027536647558L; /** Constructs the inverse of the supplied exponentional transform. */ Base10(final ExponentialTransform1D inverse) { super(inverse); } /** Creates a new instance with the given offset. */ protected Base10(final double offset) { super(10, offset); } /** {@inheritDoc} */ @Override public double transform(final double value) { return Math.log10(value) + offset; } /** {@inheritDoc} */ @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) (Math.log10(srcPts[srcOff++]) + offset); } } else { srcOff += numPts; dstOff += numPts; while (--numPts >= 0) { dstPts[--dstOff] = (float) (Math.log10(srcPts[srcOff++]) + offset); } } } /** {@inheritDoc} */ @Override 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++] = Math.log10(srcPts[srcOff++]) + offset; } } else { srcOff += numPts; dstOff += numPts; while (--numPts >= 0) { dstPts[--dstOff] = Math.log10(srcPts[srcOff++]) + offset; } } } } /** * Concatenates in an optimized way a {@link MathTransform} {@code other} to this * {@code MathTransform}. This implementation can optimize some concatenation with * {@link LinearTransform1D} and {@link ExponentialTransform1D}. * * @param other The math transform to apply. * @param applyOtherFirst {@code true} if the transformation order is {@code other} * followed by {@code this}, or {@code false} if the transformation order is * {@code this} followed by {@code other}. * @return The combined math transform, or {@code null} if no optimized combined * transform is available. */ @Override MathTransform concatenate(final MathTransform other, final boolean applyOtherFirst) { if (other instanceof LinearTransform) { final LinearTransform1D linear = (LinearTransform1D) other; if (applyOtherFirst) { if (linear.offset==0 && linear.scale>0) { return create(base, Math.log(linear.scale)/lnBase+offset); } } else { final double newBase = Math.pow(base, 1/linear.scale); if (!Double.isNaN(newBase)) { return create(newBase, linear.scale*offset + linear.offset); } } } else if (other instanceof ExponentialTransform1D) { return ((ExponentialTransform1D) other).concatenateLog(this, !applyOtherFirst); } return super.concatenate(other, applyOtherFirst); } /** * 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 = serialVersionUID + Double.doubleToLongBits(base); code = code*37 + Double.doubleToLongBits(offset); 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 LogarithmicTransform1D that = (LogarithmicTransform1D) object; return Double.doubleToLongBits(this.base) == Double.doubleToLongBits(that.base) && Double.doubleToLongBits(this.offset) == Double.doubleToLongBits(that.offset); } return false; } /** * The provider for the {@link LogarithmicTransform1D}. * * @version $Id$ * @author Martin Desruisseaux (IRD) */ public static class Provider extends MathTransformProvider { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -7235097164208708484L; /** * The operation parameter descriptor for the {@link #base base} parameter value. * Valid values range from 0 to infinity. The default value is 10. */ public static final ParameterDescriptor<Double> BASE = DefaultParameterDescriptor.create( "base", 10, 0, Double.POSITIVE_INFINITY, Unit.ONE); /** * The operation parameter descriptor for the {@link #offset offset} parameter value. * Valid values range is unrestricted. The default value is 0. */ public static final ParameterDescriptor<Double> OFFSET =DefaultParameterDescriptor.create( "offset", 0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Unit.ONE); /** * The parameters group. */ static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { new NamedIdentifier(Citations.GEOTOOLS, Vocabulary.formatInternational( VocabularyKeys.LOGARITHMIC)) }, new ParameterDescriptor[] { BASE, OFFSET }); /** * Create a provider for logarithmic transforms. */ public Provider() { super(1, 1, PARAMETERS); } /** * Returns the operation type. */ @Override public Class<Conversion> getOperationType() { return Conversion.class; } /** * Creates a logarithmic 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 MathTransform1D createMathTransform(final ParameterValueGroup values) throws ParameterNotFoundException { return create(doubleValue(BASE, values), doubleValue(OFFSET, values)); } } }