/*
* 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 exponentional 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 #scale}×{@linkplain #base}<sup><var>x</var></sup></p>
*
* This equation may be written in other form:
*
* <p align="center">{@linkplain #base}<sup><var>a</var> + <var>b</var>×<var>x</var></sup> =
* {@linkplain #base}<sup><var>a</var></sup>×({@linkplain #base}<sup><var>b</var></sup>)<sup><var>x</var></sup></p>
*
* @since 2.0
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see LogarithmicTransform1D
* @see LinearTransform1D
*/
public class ExponentialTransform1D extends AbstractMathTransform
implements MathTransform1D, Serializable
{
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 5331178990358868947L;
/**
* The base to be raised to a power.
*/
public final double base;
/**
* Natural logarithm of {@link #base}.
*/
final double lnBase;
/**
* The scale value to be multiplied.
*/
public 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 exponentional transform which is the
* inverse of the supplied logarithmic transform.
*/
ExponentialTransform1D(final LogarithmicTransform1D inverse) {
this.base = inverse.base;
this.lnBase = inverse.lnBase;
this.scale = Math.pow(base, -inverse.offset);
this.inverse = inverse;
}
/**
* Constructs a new exponentional 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 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 exponentional transform.
*
* @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 LinearTransform1D.create(0, 0);
}
if (base == 1) {
return LinearTransform1D.create(0, scale);
}
return new ExponentialTransform1D(base, scale);
}
/**
* 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.SCALE, scale)});
}
/**
* 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 = LogarithmicTransform1D.create(this);
}
return inverse;
}
/**
* Gets the derivative of this function at a value.
*/
public double derivative(final double value) {
return lnBase * transform(value);
}
/**
* Transforms the specified value.
*/
public double transform(final double value) {
return scale * Math.pow(base, value);
}
/**
* Transforms a list of coordinate point ordinal values.
*/
@Override
public void transform(float[] srcPts, int srcOff,
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 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++] = scale*Math.pow(base, srcPts[srcOff++]);
}
} else {
srcOff += numPts;
dstOff += 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 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) {
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.concatenate(other, applyOtherFirst);
}
/**
* 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.
*/
MathTransform concatenateLog(final LogarithmicTransform1D other, final boolean applyOtherFirst) {
if (applyOtherFirst) {
final double newScale = scale*Math.pow(base, other.offset);
final double newPower = lnBase/other.lnBase;
if (!Double.isNaN(newScale)) {
if (newPower == 1) {
return LinearTransform1D.create(newScale, 0);
}
// TODO: Needs a transform here with the following equation:
//
// y(x) = newScale * Math.pow(x, newPower);
}
} else if (scale > 0) {
return LinearTransform1D.create(lnBase/other.lnBase,
Math.log(scale)/other.lnBase + other.offset);
}
return null;
}
/**
* 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(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 ExponentialTransform1D that = (ExponentialTransform1D) object;
return Double.doubleToLongBits(this.base) == Double.doubleToLongBits(that.base) &&
Double.doubleToLongBits(this.scale) == Double.doubleToLongBits(that.scale);
}
return false;
}
/**
* The provider for the {@link ExponentialTransform1D}.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public static class Provider extends MathTransformProvider {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -5838840021166379987L;
/**
* 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 BASE = LogarithmicTransform1D.Provider.BASE;
/**
* The operation parameter descriptor for the {@link #scale scale} parameter value.
* Valid values range is unrestricted. The default value is 1.
*/
public static final ParameterDescriptor SCALE = DefaultParameterDescriptor.create(
"scale", 1, 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.EXPONENTIAL))
}, new ParameterDescriptor[] {
BASE, SCALE
});
/**
* 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(SCALE, values));
}
}
}