blob: 7ef09b1b619db8f2c26371f2e6fbbcc47dad8e1a [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.util.ComparisonMode;
/**
* A one dimensional exponential transform.
* Input values <var>x</var> are converted into output values <var>y</var> using the following equation:
*
* <blockquote><var>y</var> = {@linkplain #scale}⋅{@linkplain #base}<sup><var>x</var></sup></blockquote>
*
* <div class="note"><b>Tip:</b>
* if a linear transform is applied before this exponential transform, then the equation can be rewritten as:
* <var>scale</var>⋅<var>base</var><sup><var>a</var> + <var>b</var>⋅<var>x</var></sup> =
* <var>scale</var>⋅<var>base</var><sup><var>a</var></sup>⋅(<var>base</var><sup><var>b</var></sup>)<sup><var>x</var></sup>
*
* It is possible to find back the coefficients of the original linear transform by
* pre-concatenating a logarithmic transform before the exponential one, as below:
*
* {@preformat java
* LinearTransform1D linear = MathTransforms.create(exponentialTransform,
* LogarithmicTransform1D.create(base, -Math.log(scale) / Math.log(base)));
* }
* </div>
*
* <div class="section">Serialization</div>
* 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
*/
final class ExponentialTransform1D extends AbstractMathTransform1D implements Serializable {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 5331178990358868947L;
/**
* The base to be raised to a power.
*/
final double base;
/**
* Natural logarithm of {@link #base}, used for {@link #derivative(double)} computation.
*/
final double lnBase;
/**
* The scale value to be multiplied.
*
* <div class="note">The scale could be handled by a concatenation with {@link LinearTransform1D} instead than
* an explicit field in this class. However the <var>scale</var>⋅<var>base</var><sup><var>x</var></sup> formula
* is extensively used as a <cite>transfer function</cite> in grid coverages. Consequently we keep this explicit
* field for performance reasons.</div>
*/
final double scale;
/**
* 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 exponential transform which is the inverse of the supplied logarithmic transform.
*/
ExponentialTransform1D(final LogarithmicTransform1D inverse) {
this.base = inverse.base();
this.lnBase = inverse.lnBase();
this.scale = inverse.pow(-inverse.offset());
this.inverse = inverse;
}
/**
* Constructs a new exponential transform. This constructor is provided for subclasses only.
* Instances should be created using the {@linkplain #create(double, double) factory method},
* which may returns optimized implementations for some particular argument values.
*
* @param base the base to be raised to a power.
* @param scale the scale value to be multiplied.
*/
protected ExponentialTransform1D(final double base, final double scale) {
this.base = base;
this.scale = scale;
this.lnBase = Math.log(base);
}
/**
* Constructs a new exponential transform which include the given scale factor applied after the exponentiation.
*
* @param base the base to be raised to a power.
* @param scale the scale value to be multiplied.
* @return the math transform.
*/
public static MathTransform1D create(final double base, final double scale) {
if (base == 0 || scale == 0) {
return ConstantTransform1D.ZERO;
}
if (base == 1) {
return LinearTransform1D.create(scale, 0);
}
return new ExponentialTransform1D(base, scale);
}
/**
* Returns the inverse of this transform.
*/
@Override
public synchronized MathTransform1D inverse() {
if (inverse == null) {
inverse = LogarithmicTransform1D.create(this);
// Above method will set LogarithmicTransform1D.inverse = this.
}
return inverse;
}
/**
* Gets the derivative of this function at a value.
*/
@Override
public double derivative(final double value) {
return lnBase * transform(value);
}
/**
* Transforms the specified value.
*/
@Override
public double transform(final double value) {
return scale * Math.pow(base, 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++] = scale * Math.pow(base, srcPts[srcOff++]);
}
} else {
srcOff += numPts;
dstOff += numPts;
while (--numPts >= 0) {
dstPts[--dstOff] = scale * Math.pow(base, 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) (scale * Math.pow(base, srcPts[srcOff++]));
}
} else {
srcOff += numPts;
dstOff += numPts;
while (--numPts >= 0) {
dstPts[--dstOff] = (float) (scale * Math.pow(base, 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) (scale * Math.pow(base, 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++] = scale * Math.pow(base, srcPts[srcOff++]);
}
}
/**
* 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 LogarithmicTransform1D}.
*
* @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 LinearTransform) {
final LinearTransform1D linear = (LinearTransform1D) other;
if (applyOtherFirst) {
final double newBase = Math.pow(base, linear.scale);
final double newScale = Math.pow(base, linear.offset) * scale;
if (!Double.isNaN(newBase) && !Double.isNaN(newScale)) {
return create(newBase, newScale);
}
} else {
if (linear.offset == 0) {
return create(base, scale * linear.scale);
}
}
} else if (other instanceof LogarithmicTransform1D) {
return concatenateLog((LogarithmicTransform1D) other, applyOtherFirst);
}
return super.tryConcatenate(applyOtherFirst, other, factory);
}
/**
* Concatenates in an optimized way a {@link LogarithmicTransform1D} {@code other}
* to this {@code 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.
*/
final MathTransform concatenateLog(final LogarithmicTransform1D other, final boolean applyOtherFirst) {
final double newScale = lnBase / other.lnBase();
if (applyOtherFirst) {
return MathTransforms.concatenate(PowerTransform1D.create(newScale),
LinearTransform1D.create(scale * Math.pow(base, other.offset()), 0));
} else {
final double newOffset;
if (scale > 0) {
newOffset = other.log(scale) + other.offset();
} else {
/*
* Maybe the Math.log(double) argument will become
* positive if we rewrite the equation that way...
*/
newOffset = other.log(scale * other.offset() * other.lnBase());
}
if (!Double.isNaN(newOffset)) {
return LinearTransform1D.create(newScale, newOffset);
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
protected int computeHashCode() {
return Long.hashCode(Double.doubleToLongBits(base)
+ 31 * Double.doubleToLongBits(scale)) ^ 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)) {
final ExponentialTransform1D that = (ExponentialTransform1D) object;
return Numerics.equals(this.base, that.base) &&
Numerics.equals(this.scale, that.scale);
}
return false;
}
}