blob: 65c4e3fce3826581a4679878e4fab8d9e8ecf461 [file] [log] [blame]
/*
* 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;
}
}
}