| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.referencing.operation.transform; |
| |
| import java.io.Serializable; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.MathTransform1D; |
| import org.opengis.referencing.operation.MathTransformFactory; |
| import org.opengis.util.FactoryException; |
| import org.apache.sis.internal.util.Numerics; |
| import org.apache.sis.math.MathFunctions; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.ComparisonMode; |
| |
| |
| /** |
| * A one dimensional, logarithmic transform. This transform is the inverse of {@link ExponentialTransform1D}. |
| * The default implementation computes the natural logarithm of input values using {@link Math#log(double)}. |
| * Subclasses compute alternate logarithms, for example in base 10 computed by {@link Math#log10(double)}. |
| * |
| * <p>Logarithms in bases other than <var>e</var> or 10 are computed by concatenating a linear transform, |
| * using the following mathematical identity:</p> |
| * |
| * <blockquote>log<sub>base</sub>(<var>x</var>) = ln(<var>x</var>) / ln(base)</blockquote> |
| * |
| * <h2>Serialization</h2> |
| * Serialized instances of this class are not guaranteed to be compatible with future SIS versions. |
| * Serialization should be used only for short term storage or RMI between applications running the |
| * same SIS version. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 0.5 |
| * @since 0.5 |
| * @module |
| */ |
| class LogarithmicTransform1D extends AbstractMathTransform1D implements Serializable { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = 1535101265352133948L; |
| |
| /** |
| * The unique instance of the natural logarithmic transform. |
| */ |
| private static final LogarithmicTransform1D NATURAL = new LogarithmicTransform1D(); |
| |
| /** |
| * 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. |
| * |
| * @see #create(double, double) |
| */ |
| LogarithmicTransform1D() { |
| } |
| |
| /** |
| * Constructs a new logarithmic transform which add the given offset after the logarithm. |
| * |
| * @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) { |
| ArgumentChecks.ensureStrictlyPositive("base", base); |
| if (base == 10) { |
| return Base10.create(offset); |
| } else { |
| return NATURAL.concatenate(1 / Math.log(base), offset); |
| } |
| } |
| |
| /** |
| * Constructs a new logarithmic transform which is the inverse of the supplied exponential transform. |
| */ |
| static MathTransform1D create(final ExponentialTransform1D inverse) { |
| if (inverse.base == 10) { |
| return Base10.create(-Math.log10(inverse.scale)); |
| } else { |
| return NATURAL.concatenate(1 / inverse.lnBase, -Math.log(inverse.scale) / inverse.lnBase); |
| } |
| } |
| |
| /** |
| * Returns the concatenation of this transform by the given scale and offset. |
| * This method does not check if a simplification is possible. |
| */ |
| private MathTransform1D concatenate(final double scale, final double offset) { |
| final LinearTransform1D t = LinearTransform1D.create(scale, offset); |
| return t.isIdentity() ? this : new ConcatenatedTransformDirect1D(this, t); |
| } |
| |
| /** |
| * 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 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}. |
| * @param other the other math transform to (pre-)concatenate with this transform. |
| * @param factory the factory which is (indirectly) invoking this method, or {@code null} if none. |
| * @return the combined math transform, or {@code null} if no optimized combined transform is available. |
| */ |
| @Override |
| protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other, |
| final MathTransformFactory factory) throws FactoryException |
| { |
| if (other instanceof LinearTransform1D) { |
| final LinearTransform1D linear = (LinearTransform1D) other; |
| if (applyOtherFirst) { |
| if (linear.offset == 0 && linear.scale > 0) { |
| return create(base(), transform(linear.scale)); |
| } |
| } else { |
| final double newBase = pow(1 / linear.scale); |
| if (!Double.isNaN(newBase)) { |
| return create(newBase, linear.transform(offset())); |
| } |
| } |
| } else if (other instanceof ExponentialTransform1D) { |
| return ((ExponentialTransform1D) other).concatenateLog(this, !applyOtherFirst); |
| } |
| return super.tryConcatenate(applyOtherFirst, other, factory); |
| } |
| |
| /** |
| * Returns the inverse of this transform. |
| */ |
| @Override |
| public synchronized MathTransform1D inverse() { |
| if (inverse == null) { |
| inverse = new ExponentialTransform1D(this); |
| // Above constructor will set ExponentialTransform1D.inverse = this. |
| } |
| return inverse; |
| } |
| |
| /** |
| * Returns the base of this logarithmic function. |
| */ |
| double base() { |
| return Math.E; |
| } |
| |
| /** |
| * Returns the natural logarithm of the base of this logarithmic function. |
| * More specifically, returns <code>{@linkplain Math#log(double) Math.log}({@link #base()})</code>. |
| */ |
| double lnBase() { |
| return 1; |
| } |
| |
| /** |
| * Returns the offset applied after this logarithmic function. |
| */ |
| double offset() { |
| return 0; |
| } |
| |
| /** |
| * Gets the derivative of this function at a value. |
| */ |
| @Override |
| public double derivative(final double value) { |
| return 1 / value; |
| } |
| |
| /** |
| * Returns the base of this logarithmic transform raised to the given power. |
| * |
| * @param value the power to raise the base. |
| * @return the base of this transform raised to the given power. |
| */ |
| double pow(final double value) { |
| return Math.exp(value); |
| } |
| |
| /** |
| * Returns the logarithm of the given value in the base of this logarithmic transform. |
| * This method is similar to {@link #transform(double)} except that the offset is not added. |
| * |
| * @param value the value for which to compute the log. |
| * @return the log of the given value in the base used by this transform. |
| */ |
| double log(final double value) { |
| return Math.log(value); |
| } |
| |
| /** |
| * Transforms the specified value. |
| */ |
| @Override |
| public double transform(final double value) { |
| return Math.log(value); |
| } |
| |
| /** |
| * Transforms many coordinates in a list of ordinal values. |
| */ |
| @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.log(srcPts[srcOff++]); |
| } |
| } else { |
| srcOff += numPts; |
| dstOff += numPts; |
| while (--numPts >= 0) { |
| dstPts[--dstOff] = Math.log(srcPts[--srcOff]); |
| } |
| } |
| } |
| |
| /** |
| * Transforms many coordinates in a list of 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++]); |
| } |
| } else { |
| srcOff += numPts; |
| dstOff += numPts; |
| while (--numPts >= 0) { |
| dstPts[--dstOff] = (float) Math.log(srcPts[--srcOff]); |
| } |
| } |
| } |
| |
| /** |
| * Transforms many coordinates in a list of ordinal values. |
| */ |
| @Override |
| public void transform(final double[] srcPts, int srcOff, final float[] dstPts, int dstOff, int numPts) { |
| while (--numPts >= 0) { |
| dstPts[dstOff++] = (float) Math.log(srcPts[srcOff++]); |
| } |
| } |
| |
| /** |
| * Transforms many coordinates in a list of ordinal values. |
| */ |
| @Override |
| public void transform(final float[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts) { |
| while (--numPts >= 0) { |
| dstPts[dstOff++] = Math.log(srcPts[srcOff++]); |
| } |
| } |
| |
| /** |
| * Special case for base 10 taking advantage of extra precision provided by {@link Math#log10(double)}. |
| */ |
| static final class Base10 extends LogarithmicTransform1D { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = -5435804027536647558L; |
| |
| /** |
| * The natural logarithm of 10. |
| */ |
| private static final double LOG_10 = 2.302585092994045684; |
| |
| /** |
| * Commonly used instance with no offset. |
| */ |
| static final Base10 INSTANCE = new Base10(0); |
| |
| /** |
| * The offset to add to the logarithm. |
| * |
| * <div class="note"><b>Note:</b> the offset could be handled by a concatenation with {@link LinearTransform1D}. |
| * instead than an explicit field in this class. However the <var>offset</var> + log<sub>base</sub>(<var>x</var>) |
| * formula is extensively used as a <cite>transfer function</cite> in grid coverages. Consequently we keep this |
| * explicit field for performance reasons.</div> |
| */ |
| private final double offset; |
| |
| /** |
| * Creates a new instance with the given offset. |
| * |
| * @see #create(double) |
| */ |
| private Base10(final double offset) { |
| this.offset = offset; |
| } |
| |
| /** |
| * Creates a new instance with the given offset. |
| */ |
| public static Base10 create(final double offset) { |
| return (offset == 0) ? INSTANCE : new Base10(offset); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| double base() { |
| return 10; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| double lnBase() { |
| return LOG_10; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| double offset() { |
| return offset; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public double derivative(final double value) { |
| return (1 / LOG_10) / value; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| double pow(final double value) { |
| return MathFunctions.pow10(value); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| double log(final double value) { |
| return Math.log10(value); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public double transform(final double value) { |
| return Math.log10(value) + 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; |
| } |
| } |
| } |
| |
| /** {@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 float[] dstPts, int dstOff, int numPts) { |
| while (--numPts >= 0) { |
| dstPts[dstOff++] = (float) (Math.log10(srcPts[srcOff++]) + offset); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void transform(final float[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts) { |
| while (--numPts >= 0) { |
| dstPts[dstOff++] = Math.log10(srcPts[srcOff++]) + offset; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected int computeHashCode() { |
| return Double.hashCode(offset) ^ super.computeHashCode(); |
| } |
| |
| /** Compares the specified object with this math transform for equality. */ |
| @Override |
| public boolean equals(final Object object, final ComparisonMode mode) { |
| if (object == this) { |
| return true; // Optimization for a common case. |
| } |
| if (super.equals(object, mode)) { |
| return Numerics.equals(offset, ((Base10) object).offset); |
| } |
| return false; |
| } |
| } |
| } |