sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1737448 - in /sis/branches/JDK8/core: sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ sis-utility/src/main/java/org/apache/sis/util...
Date Fri, 01 Apr 2016 22:21:19 GMT
Author: desruisseaux
Date: Fri Apr  1 22:21:18 2016
New Revision: 1737448

URL: http://svn.apache.org/viewvc?rev=1737448&view=rev
Log:
LinearInterpolator1D refactoring:
- replace the loop over y values by an use Arrays.binarySearch.
- split the work on x and y values in two separated class:
  - one from x to indices (the Invert inner class)
  - one from indices to y values (the outer class)
The rational for this split is that most of our usage (at least in the context of Coverage's
"gridToCRS") use only
the second part. Indeed, the previous static factory method was allowing construction of only
that second part.

Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform1D.java
[UTF-8] Fri Apr  1 22:21:18 2016
@@ -150,4 +150,82 @@ public abstract class AbstractMathTransf
     public MathTransform1D inverse() throws NoninvertibleTransformException {
         return (MathTransform1D) super.inverse();
     }
+
+    /**
+     * Base class for implementation of inverse math transforms.
+     * This inner class is the inverse of the enclosing {@link AbstractMathTransform1D}.
+     *
+     * <div class="section">Serialization</div>
+     * Instances of this class are serializable only if the enclosing math transform is also
serializable.
+     * Serialized math transforms are not guaranteed to be compatible with future SIS versions.
+     * Serialization, if allowed, should be used only for short term storage or RMI between
applications
+     * running the same SIS version.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @since   0.7
+     * @version 0.7
+     * @module
+     */
+    protected abstract class Inverse extends AbstractMathTransform.Inverse implements MathTransform1D
{
+        /**
+         * Serial number for inter-operability with different versions.
+         */
+        private static final long serialVersionUID = 2018412413506158560L;
+
+        /**
+         * Constructs an inverse math transform.
+         */
+        protected Inverse() {
+            AbstractMathTransform1D.this.super();
+        }
+
+        /**
+         * Returns the enclosing math transform.
+         */
+        @Override
+        public MathTransform1D inverse() {
+            return (MathTransform1D) super.inverse();
+        }
+
+        /**
+         * Transforms a single point in the given array and opportunistically computes its
derivative if requested.
+         * The default implementation delegates to {@link #transform(double)} and potentially
to {@link #derivative(double)}.
+         * Subclasses may override this method for performance reason.
+         *
+         * @return {@inheritDoc}
+         * @throws TransformException {@inheritDoc}
+         */
+        @Override
+        public Matrix transform(final double[] srcPts, final int srcOff,
+                                final double[] dstPts, final int dstOff,
+                                final boolean derivate) throws TransformException
+        {
+            final double ordinate = srcPts[srcOff];
+            if (dstPts != null) {
+                dstPts[dstOff] = transform(ordinate);
+            }
+            return derivate ? new Matrix1(derivative(ordinate)) : null;
+        }
+
+        /**
+         * Gets the derivative of this transform at a point. The default implementation ensures
that
+         * {@code point} is one-dimensional, then delegates to {@link #derivative(double)}.
+         *
+         * @param  point The coordinate point where to evaluate the derivative, or {@code
null}.
+         * @return The derivative at the specified point (never {@code null}).
+         * @throws MismatchedDimensionException if {@code point} does not have the expected
dimension.
+         * @throws TransformException if the derivative can not be evaluated at the specified
point.
+         */
+        @Override
+        public Matrix derivative(final DirectPosition point) throws TransformException {
+            final double ordinate;
+            if (point == null) {
+                ordinate = Double.NaN;
+            } else {
+                ensureDimensionMatches("point", 1, point);
+                ordinate = point.getOrdinate(0);
+            }
+            return new Matrix1(derivative(ordinate));
+        }
+    }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java
(original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java
Fri Apr  1 22:21:18 2016
@@ -17,133 +17,290 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.Arrays;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ArraysExt;
+import java.io.Serializable;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
-import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.resources.Errors;
+
 
 /**
- * {@link MathTransform1D} with linear interpolation between values.
+ * A transform that performs linear interpolation between values.
+ * The transform is invertible if, and only if, the values are in increasing order.
+ *
+ * <p>If desired values in decreasing order can be supported by inverting the sign
of all values,
+ * then concatenating this transform with a transform that multiply all output values by
-1.</p>
  *
- * @author Johann Sorel (Geomatys)
- * @author Remi Marechal (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @author  Remi Marechal (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
  */
-final class LinearInterpolator1D extends AbstractMathTransform1D {
-    private final double[] antecedent;
+final class LinearInterpolator1D extends AbstractMathTransform1D implements Serializable
{
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -5025693608589996896L;
+
+    /**
+     * The sequence values specified at construction time.
+     */
     private final double[] values;
-    private boolean isIncreaseOrder = true;
-    private final int l;
 
     /**
-     * <p>In this case an antecedents default table is construct such as : <br>
+     * The average function slope. Used only for extrapolations.
+     */
+    private final double slope;
+
+    /**
+     * If the transform is invertible, the inverse. Otherwise {@code null}.
+     * The transform is invertible only if values are in increasing order.
+     */
+    private final MathTransform1D inverse;
+
+    /**
+     * Creates a new transform which will interpolate in the given table of values.
+     * The inputs are {0, 1, … , <var>N</var>} where <var>N</var>
is length of output values.
      *
-     * [0, 1, ... , N] with N is length of image table values.<p>
+     * <p>This constructor assumes that the {@code values} array have already be clones,
+     * so it will not clone it again.</p>
      *
-     * @param values image from antecedents table values.
+     * @param values the <var>y</var> values in <var>y=f(x)</var>
where <var>x</var> = {0, 1, … , {@code values.length-1}}.
+     * @param slope  the value to use for extrapolation.
      */
-    public LinearInterpolator1D(double[] values) {
-        ArgumentChecks.ensureNonNull("values", values);
-        this.l          = values.length;
-        if (l < 2)
-            throw new IllegalArgumentException("table must have more than only two values");
-        this.values     = values;
-        this.antecedent = new double[l];
-        for (int v = 0;v<l;v++) this.antecedent[v] = v;
+    private LinearInterpolator1D(final double[] values, final double slope) {
+        this.values = values;                           // Cloning this array is caller's
responsibility.
+        this.slope  = slope;
+        double last = values[0];
+        for (int i=1; i<values.length; i++) {
+            if (!(last <= (last = values[i]))) {        // Use '!' for catching NaN values.
+                inverse = null;                         // Transform is not reversible.
+                return;
+            }
+        }
+        inverse = new Inverse();
     }
 
     /**
-     * Two tables such as : f(antecedents) = values.
+     * Creates a transform for the given values. This method returns an affine transform
instead than an
+     * interpolator if the given values form a series with a constant increment.
      *
-     * @param antecedents "abscissa" table values.
-     * @param values image from antecedents table values.
+     * @param values a <strong>copy</strong> of the user-provided values. This
array may be modified.
      */
-    public LinearInterpolator1D(double[] antecedents, double[] values) {
-        ArgumentChecks.ensureNonNull("values", values);
-        ArgumentChecks.ensureNonNull("antecedents", antecedents);
-        this.l = antecedents.length;
-        if (l<2)
-            throw new IllegalArgumentException("table must have more than only two values");
-        if (l != values.length)
-            throw new IllegalArgumentException("antecedents and values table must have same
length");
-        if (!ArraysExt.isSorted(antecedents, true)) {
-            final double[] antecedent2 = new double[l];
-            int id = l;
-            for (int i = 0; i < l; i++) antecedent2[i] = antecedents[--id];
-            if (!ArraysExt.isSorted(antecedent2, true))
-                throw new IllegalArgumentException("antecedents table must be strictly increasing
or decreasing");
-            isIncreaseOrder = false;
-        }
-        this.antecedent = antecedents;
-        this.values     = values;
+    private static MathTransform1D create(final double[] values) {
+        final int n = values.length - 1;
+        final double offset = values[0];
+        double slope = (values[n] - offset) / n;
+        /*
+         * If the increment between values is constant (with a small tolerance factor),
+         * return a one-dimensional affine transform instead than an interpolator.
+         * We need to perform this check before the sign reversal applied after this loop.
+         */
+        final double tolerance = Math.abs(slope) * Numerics.COMPARISON_THRESHOLD;
+        for (int i=0; Numerics.epsilonEqual(values[++i]-offset, slope*i, tolerance);) {
+            if (i >= n) {
+                return LinearTransform1D.create(slope, offset);
+            }
+        }
+        /*
+         * If the values are in decreasing order, reverse their sign so we get increasing
order.
+         * We will multiply the results by -1 after the transformation.
+         */
+        final boolean isReverted = (slope < 0);
+        if (isReverted) {
+            slope = -slope;
+            for (int i=0; i<=n; i++) {
+                values[i] = -values[i];
+            }
+        }
+        MathTransform1D tr = new LinearInterpolator1D(values, slope);
+        if (isReverted) {
+            tr = new ConcatenatedTransformDirect1D(tr, LinearTransform1D.NEGATE);
+        }
+        return tr;
     }
 
     /**
-     * {@inheritDoc }.
-     */
-    @Override
-    public double transform(double d) throws TransformException {
-        int ida, idb, idn, idn1;
-        if (isIncreaseOrder) {
-            ida = 0; idb  = l-1;
-            idn = 0; idn1 = 1;
+     * Creates a <i>y=f(x)</i> transform for the given <var>x</var>
and <var>y</var> values.
+     * See {@link MathTransforms#interpolate(double[], double[])} javadoc for more information.
+     */
+    static MathTransform1D create(final double[] x, final double[] y) {
+        final int length;
+        if (x == null) {
+            if (y == null) {
+                return IdentityTransform1D.INSTANCE;
+            }
+            length = y.length;
         } else {
-            ida = l-1; idb  = 0;
-            idn = 1;   idn1 = 0;
+            length = x.length;
+            if (y != null && y.length != length) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLengths));
+            }
+        }
+        switch (length) {
+            case 0: throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1,
(x != null) ? "x" : "y"));
+            case 1: return LinearTransform1D.constant((x != null) ? x[0] : Double.NaN, (y
!= null) ? y[0] : Double.NaN);
+        }
+        /*
+         * A common usage of this 'create' method is for creating a "gridToCRS" transform
from grid coordinates
+         * to something else, in which case the 'x' array is null. In the less frequent case
where x is non-null,
+         * we first convert from x values to indices, then from indices to y values.
+         */
+        MathTransform1D tr = null;
+        if (y != null) {
+            tr = create(y.clone());
+        }
+        if (x != null) {
+            final MathTransform1D indexToY = tr;
+            try {
+                tr = create(x.clone()).inverse();                                       //
xToIndex transform.
+            } catch (NoninvertibleTransformException e) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.NonMonotonicSequence_1,
"x"), e);
+            }
+            if (indexToY != null) {
+                tr = MathTransforms.concatenate(tr, indexToY);
+            }
+        }
+        return tr;
+    }
+
+    /**
+     * Returns {@code true} if this transform is the identity transform. This method should
never returns {@code true}
+     * since we verified the inputs in the {@code create(…)} method. We nevertheless
verify as a paranoiac safety.
+     */
+    @Override
+    public boolean isIdentity() {
+        for (int i=0; i<values.length; i++) {
+            if (values[i] != i) return false;
         }
-        if (d <= antecedent[ida]) return values[ida];
-        if (d >= antecedent[idb]) return values[idb];
-        double x0, x1;
-        for (int id = 0; id < l-1; id++) {
-            x0 = antecedent[idn];
-            x1 = antecedent[idn1];
-            if      (d == x0) return values[idn];
-            else if (d == x1) return values[idn1];
-            else if (d > antecedent[idn] && d < antecedent[idn1])
-                return values[idn] + (values[idn1] - values[idn]) * ((d-x0) / (x1-x0));
-            idn++;idn1++;
-        }
-        return 0;//impossible
+        return true;
     }
 
     /**
-     * {@inheritDoc }.
-     * Note : for each segment lower sequence point derivative is inclusive
-     * whereas upper sequence point is exclusive.
-     */
-    @Override
-    public double derivative(double d) throws TransformException {
-        int idn, idn1;
-        if (isIncreaseOrder) {
-            idn  = 0; idn1 = 1;
-            if (d < antecedent[0] || d >= antecedent[l-1]) return 0;
+     * Interpolates a <var>y</var> values for the given <var>x</var>.
+     * The given <var>x</var> value should be between 0 to {@code values.length
- 1} inclusive.
+     * If the given input value is outside that range, then the output value will be extrapolated.
+     */
+    @Override
+    public double transform(double x) {
+        if (x >= 0) {
+            final int i = (int) x;
+            final int n = values.length - 1;
+            if (i < n) {
+                x -= i;
+                return values[i] * (1-x) + values[i+1] * x;
+            }
+            // x is after the last available value.
+            return (x - n) * slope + values[n];
         } else {
-            idn  = 1; idn1 = 0;
-            if (d <= antecedent[l-1] || d > antecedent[0]) return 0;
+            // x is before the first available value.
+            return x * slope + values[0];
         }
-        for (int id = 0; id < l; id++) {
-            if ((d > antecedent[idn]  && d < antecedent[idn1]) || d == antecedent[id])
-                return (values[idn1]-values[idn]) / (antecedent[idn1]-antecedent[idn]);
-            idn++;idn1++;
+    }
+
+    /**
+     * Returns the derivative of <var>y</var> for the given <var>x</var>.
+     * Note: for each segment, the derivative is considered constant between
+     * <var>x</var> inclusive and <var>x+1</var> exclusive.
+     */
+    @Override
+    public double derivative(final double x) {
+        if (x >= 0) {
+            final int i = (int) x;
+            if (i < values.length - 1) {
+                return values[i+1] - values[i];
+            }
         }
-        return 0;//impossible
+        return slope;
     }
 
+    /**
+     * Returns the inverse of this transform, or throw an exception if there is no inverse.
+     */
     @Override
     public MathTransform1D inverse() throws NoninvertibleTransformException {
-        if (!ArraysExt.isSorted(values, true)) {
-            final double[] values2 = new double[l];
-            int id = l;
-            for (int i = 0; i < l; i++) values2[i] = values[--id];
-            if (!ArraysExt.isSorted(values2, true))
-                throw new NoninvertibleTransformException("non inversible");
+        return (inverse != null) ? inverse : super.inverse();
+    }
+
+    /**
+     * The inverse of the enclosing {@link LinearInterpolator1D}. Given a <var>y</var>
value, this class performs
+     * a bilinear search for locating the lower and upper <var>x</var> values
as integers, then interpolates the
+     * <var>x</var> real value.
+     */
+    private final class Inverse extends AbstractMathTransform1D.Inverse implements MathTransform1D
{
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 3179638888992528901L;
+
+        /**
+         * Creates a new inverse transform.
+         */
+        Inverse() {
+        }
+
+        /**
+         * Locates by bilinear search and interpolates the <var>x</var> value
for the given <var>y</var>.
+         */
+        @Override
+        public double transform(final double y) {
+            final double[] values = LinearInterpolator1D.this.values;
+            int x = Arrays.binarySearch(values, y);
+            if (x >= 0) {
+                return x;
+            }
+            x = ~x;
+            if (x >= 1) {
+                if (x < values.length) {
+                    final double y0 = values[x-1];
+                    return (y - y0) / (values[x] - y0) + (x-1);
+                } else {
+                    // y is after the last available value.
+                    final int n = values.length - 1;
+                    return (y - values[n]) / slope + n;
+                }
+            } else {
+                // y is before the first available value.
+                return (y - values[0]) / slope;
+            }
+        }
+
+        /**
+         * Returns the derivative at the given <var>y</var> value.
+         */
+        @Override
+        public double derivative(final double y) {
+            final double[] values = LinearInterpolator1D.this.values;
+            int x = Arrays.binarySearch(values, y);
+            if (x < 0) {
+                x = ~x;
+            }
+            if (x >= 1 && x < values.length) {
+                return 1 / (values[x] - values[x-1]);
+            }
+            return 1 / slope;
         }
-        return new LinearInterpolator1D(values, antecedent);
     }
 
+    /**
+     * Computes a hash code value for this transform.
+     */
     @Override
-    public boolean isIdentity() {
-        return Arrays.equals(antecedent, values);
+    protected int computeHashCode() {
+        return Arrays.hashCode(values) ^ Numerics.hashCode(Double.doubleToLongBits(slope));
     }
 
+    /**
+     * Compares this transform with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (super.equals(object, mode)) {
+            return Arrays.equals(values, ((LinearInterpolator1D) object).values);
+        }
+        return false;
+    }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearTransform1D.java
[UTF-8] Fri Apr  1 22:21:18 2016
@@ -47,7 +47,7 @@ import static java.lang.Double.doubleToR
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.5
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see LogarithmicTransform1D
@@ -60,6 +60,11 @@ class LinearTransform1D extends Abstract
     private static final long serialVersionUID = -7595037195668813000L;
 
     /**
+     * A transform that just reverse the sign of input values.
+     */
+    static final LinearTransform1D NEGATE = new LinearTransform1D(-1, 0);
+
+    /**
      * The value which is multiplied to input values.
      */
     final double scale;
@@ -99,8 +104,9 @@ class LinearTransform1D extends Abstract
      * @see MathTransforms#linear(double, double)
      */
     public static LinearTransform1D create(final double scale, final double offset) {
-        if (offset == 0 && scale == 1) {
-            return IdentityTransform1D.INSTANCE;
+        if (offset == 0) {
+            if (scale == +1) return IdentityTransform1D.INSTANCE;
+            if (scale == -1) return NEGATE;
         }
         if (scale == 0) {
             if (offset == 0) return ConstantTransform1D.ZERO;
@@ -111,6 +117,19 @@ class LinearTransform1D extends Abstract
     }
 
     /**
+     * Creates a constant function having value <var>y</var>, and for which the
inverse is <var>x</var>.
+     *
+     * @since 0.7
+     */
+    static LinearTransform1D constant(final double x, final double y) {
+        final LinearTransform1D tr = create(0, y);
+        if (!Double.isNaN(x)) {
+            tr.inverse = create(0, x);
+        }
+        return tr;
+    }
+
+    /**
      * Returns the parameter descriptors for this math transform.
      */
     @Override

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
[UTF-8] Fri Apr  1 22:21:18 2016
@@ -147,6 +147,33 @@ public final class MathTransforms extend
     }
 
     /**
+     * Creates a transform for the <i>y=f(x)</i> function where <var>y</var>
are computed by a linear interpolation.
+     * Both {@code x} and {@code y} arguments can be null:
+     *
+     * <ul>
+     *   <li>If both {@code x} and {@code y} arrays are non-null, then the must have
the same length.</li>
+     *   <li>If both {@code x} and {@code y} arrays are null, then this method returns
the identity transform.</li>
+     *   <li>If only {@code x} is null, then the <var>x</var> values are
taken as {0, 1, 2, …, {@code y.length} - 1}.</li>
+     *   <li>If only {@code y} is null, then the <var>y</var> values are
taken as {0, 1, 2, …, {@code x.length} - 1}.</li>
+     * </ul>
+     *
+     * All <var>x</var> values shall be real numbers (not NaN) sorted in increasing
or decreasing order.
+     * Elements in the {@code y} array do not need to be ordered, but the returned transform
will be invertible
+     * only if all <var>y</var> values are real numbers sorted in increasing
or decreasing order.
+     * Furthermore the returned transform is affine (i.e. implement the {@link LinearTransform}
interface)
+     * if the interval between each <var>x</var> and <var>y</var>
value is constant.
+     *
+     * @param x the input values in the function domain, or {@code null}.
+     * @param y the output values in the function range, or {@code null}.
+     * @return the <i>y=f(x)</i> function.
+     *
+     * @since 0.7
+     */
+    public static MathTransform1D interpolate(final double[] x, final double[] y) {
+        return LinearInterpolator1D.create(x, y);
+    }
+
+    /**
      * Puts together a list of independent math transforms, each of them operating on a subset
of ordinate values.
      * This method is often used for defining 4-dimensional (<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>)
      * transform as an aggregation of 3 simpler transforms operating on (<var>x</var>,<var>y</var>),
(<var>z</var>)
@@ -419,55 +446,4 @@ public final class MathTransforms extend
         }
         return derivative;
     }
-
-    /**
-     * Create an 1D Transform for the given serie.
-     * <br>
-     * Values are expected to be in increasing or decreasing order and must
-     * not contain duplicates.
-     * <br>
-     * This method first try to find a regular scale between values to create a
-     * linear transform, otherwise a non linear transform is created.
-     *
-     * @param serie value serie
-     * @return MathTransform1D may not be linear
-     * @throws org.opengis.referencing.operation.TransformException
-     */
-    public static MathTransform1D create1D(final double[] serie) throws TransformException
{
-
-        if(serie.length==0){
-            throw new TransformException("Serie can not be empty");
-        }else if(serie.length==1){
-            //only one value, we use it as an offset
-            return (MathTransform1D) linear(1.0, serie[0]);
-        }
-
-        //search for a linear scale and verify values
-        boolean linear = true;
-        final double scale = serie[1]-serie[0];
-        final double sign = Math.signum(scale);
-        if(sign == 0.0) throw new TransformException("Serie contains a duplicated value at
index 0 and 1");
-
-        double candidate;
-        for(int i=1;i<serie.length-1;i++){
-            candidate = serie[i+1]-serie[i];
-            if(candidate == 0.0){
-                throw new TransformException("Serie contains a duplicated value at index
"+i+" and "+(i+1));
-            }
-            if(scale != candidate){
-                linear = false;
-                if(sign != Math.signum(candidate)){
-                    throw new TransformException("Serie is not sorted at index "+i+" and
"+(i+1));
-                }
-            }
-        }
-
-        if(linear){
-            return (MathTransform1D) linear(scale, serie[0]);
-        }else{
-            //unlinear transform
-            return new LinearInterpolator1D(serie);
-        }
-    }
-
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
(original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
Fri Apr  1 22:21:18 2016
@@ -16,178 +16,226 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import org.apache.sis.test.TestCase;
-import org.junit.Assert;
-import static org.junit.Assert.assertTrue;
-import org.junit.Test;
+import java.util.Random;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.test.referencing.TransformTestCase;
+import org.junit.Test;
+
+import static org.opengis.test.Assert.*;
+
 
 /**
  * Test {@link LinearInterpolator1D} class.
  *
- * @author Remi Marechal (Geomatys).
+ * @author  Remi Marechal (Geomatys).
+ * @author  Martin Desruisseaux (Geomatys).
+ * @since   0.7
+ * @version 0.7
+ * @module
  */
-public class LinearInterpolator1DTest extends TestCase {
-
-    private double[] antecedent, values;
+public final strictfp class LinearInterpolator1DTest extends TransformTestCase {
+    /**
+     * The values of the <i>y=f(x)</i> function to test.
+     */
+    private double[] x, y;
 
+    /**
+     * Creates a new test case.
+     */
     public LinearInterpolator1DTest() {
     }
 
     /**
-     * <p>antecedents in increasing order.<br>
-     * values in increasing order.</p>
-     * @throws TransformException
+     * Tests <var>x</var> values equal to indices and <var>y</var>
values in increasing order.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testIncreaseIncrease() throws TransformException{
-        antecedent = new double[]{0,1,2,3};
-        values     = new double[]{10,12,16,22};
-        testMath(true);
+    public void testIndicesToIncreasingValues() throws TransformException {
+        x = new double[] { 0,  1,  2,  3};
+        y = new double[] {10, 12, 16, 22};
+        verifyConsistency(-2, 5, -5196528645359952958L);
+        assertInstanceOf("Expected y=f(i)", LinearInterpolator1D.class, transform);
     }
 
     /**
-     * <p>antecedents in increasing order.<br>
-     * values in decreasing order.</p>
-     * @throws TransformException
+     * Tests <var>x</var> values equal to indices and <var>y</var>
values in decreasing order.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testIncreaseDecrease() throws TransformException{
-        antecedent = new double[]{0,1,2,3};
-        values     = new double[]{35,27,22,5};
-        testMath(true);
+    public void testIndicesToDecreasingValues() throws TransformException {
+        x = new double[] {0,   1,  2, 3};
+        y = new double[] {35, 27, 22, 5};
+        verifyConsistency(-2, 5, 6445394511592290678L);
+        assertInstanceOf("Expected y = -f(-i)", ConcatenatedTransformDirect1D.class, transform);
+        assertInstanceOf("Expected y = -f(-i)", LinearInterpolator1D.class, ((ConcatenatedTransform)
transform).transform1);
+        assertSame      ("Expected y = -f(-i)", LinearTransform1D.NEGATE,   ((ConcatenatedTransform)
transform).transform2);
     }
 
     /**
-     * <p>antecedents in decreasing order.<br>
-     * values in increasing order.</p>
-     * @throws TransformException
+     * Tests increasing <var>x</var> values to <var>y</var> values
that are equal to indices.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testDecreaseIncrease() throws TransformException{
-        antecedent = new double[]{2,-5,-96,-207};
-        values     = new double[]{-50,-20,7,105};
-        testMath(true);
+    public void testIncreasingInputsToIndices() throws TransformException {
+        x = new double[] {10, 12, 16, 22};
+        y = new double[] { 0,  1,  2,  3};
+        verifyConsistency(0, 30, 6130776597146077588L);
     }
 
     /**
-     * <p>antecedents in decreasing order.<br>
-     * values in decreasing order.</p>
-     * @throws TransformException
+     * Tests decreasing <var>x</var> values to <var>y</var> values
that are equal to indices.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testDecreaseDecrease() throws TransformException{
-        antecedent = new double[]{2,-5,-96,-207};
-        values     = new double[]{105,7,-19,-43};
-        testMath(true);
+    public void testDecreasingInputsToIndices() throws TransformException {
+        x = new double[] {35, 27, 22, 5};
+        y = new double[] {0,   1,  2, 3};
+        verifyConsistency(0, 40, 4109281798631024654L);
+        assertInstanceOf("Expected i = -f(-x)", ConcatenatedTransformDirect1D.class, transform);
     }
 
     /**
-     * <p>antecedents in increasing order.<br>
-     * values in random order.</p>
-     * @throws TransformException
+     * Tests increasing <var>x</var> values to increasing <var>y</var>
values.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testIncreaseRandom() throws TransformException{
-        antecedent = new double[]{-52,-27,-13,2};
-        values     = new double[]{105,-19,7,-43};
-        testMath(false);
+    public void testIncreasingInputsToIncreasingValues() throws TransformException {
+        x = new double[] { -207, -96, -5,   2};
+        y = new double[] {  -50, -20,  7, 105};
+        verifyConsistency(-210, 5, 1941178068603334535L);
+        assertInstanceOf("Expected y = f(x)", ConcatenatedTransformDirect1D.class, transform);
+        assertInstanceOf("Expected y = f(x)", LinearInterpolator1D.class, ((ConcatenatedTransform)
transform).transform2);
     }
 
     /**
-     * <p>antecedents in increasing order.<br>
-     * values in random order.</p>
-     * @throws TransformException
+     * Tests decreasing <var>x</var> values to increasing <var>y</var>
values.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testDecreaseRandom() throws TransformException{
-        antecedent = new double[]{1017,525,24,12};
-        values     = new double[]{-43,7,-19,105};
-        testMath(false);
+    public void testDecreasingInputsToIncreasingValues() throws TransformException {
+        x = new double[] {  2,  -5, -96, -207};
+        y = new double[] {-50, -20,  7,   105};
+        verifyConsistency(-210, 5, 7360962930883142147L);
+        assertInstanceOf("Expected y = -f(-x)", ConcatenatedTransformDirect1D.class, transform);
     }
 
     /**
-     * <p>antecedents in increasing order.<br>
-     * values in random order.</p>
-     * @throws TransformException
+     * Tests decreasing <var>x</var> values to decreasing <var>y</var>
values.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testDcnsPercent() throws TransformException{
-        antecedent = new double[]{5, 6.5, 8, 10, 25, 28, 30, 32};
-        values     = new double[]{100, 66, 33, 0, 0, 33, 66, 100};
-        testMath(false);
+    public void testDecreasingInputsToDecreasingValues() throws TransformException {
+        x = new double[] {  2, -5, -96, -207};
+        y = new double[] {105,  7, -19,  -43};
+        verifyConsistency(-210, 5, -2463171263749789198L);
+        assertInstanceOf("Expected y = -f(-x)", ConcatenatedTransformDirect1D.class, transform);
     }
 
     /**
-     * Test fail.
+     * Tests increasing <var>x</var> values to non-monotonic <var>y</var>
values.
+     *
+     * @throws TransformException if an error occurred while testing a value.
      */
     @Test
-    public void testFail() throws TransformException{
-        antecedent = new double[]{-43,7,-19,105};
-        values     = new double[]{1017,525,24,12};
+    public void testIncreasingInputsToNonMonotonic() throws TransformException {
+        x = new double[] {-52, -27, -13,   2};
+        y = new double[] {105, -19,   7, -43};
+        isInverseTransformSupported = false;
+        verifyConsistency(-60, 5, 7750310847358135291L);
+    }
+
+    /**
+     * Tests decreasing <var>x</var> values to non-monotonic <var>y</var>
values.
+     *
+     * @throws TransformException if an error occurred while testing a value.
+     */
+    @Test
+    public void testDecreasingInputsToNonMonotonic() throws TransformException {
+        x = new double[] {1017, 525,  24,  12};
+        y = new double[] { -43,   7, -19, 105};
+        isInverseTransformSupported = false;
+        verifyConsistency(0, 1020, 2060810396521686858L);
+    }
+
+    /**
+     * Tests increasing <var>x</var> values to non-monotonic <var>y</var>
values.
+     *
+     * @throws TransformException if an error occurred while testing a value.
+     */
+    @Test
+    public void testIncreasingInputsToPercent() throws TransformException {
+        x = new double[] {  5, 6.5,  8, 10, 25, 28, 30,  32};
+        y = new double[] {100,  66, 33,  0,  0, 33, 66, 100};
+        isInverseTransformSupported = false;
+        verifyConsistency(0, 40, -6588291548545974041L);
+    }
+
+    /**
+     * Verifies that the factory method does not accept invalid arguments.
+     */
+    @Test
+    public void testArgumentChecks() {
+        x = new double[] { -43,   7, -19, 105};                         // Non-monotonic
sequence.
+        y = new double[] {1017, 525,  24,  12};
         try {
-            MathTransform1D mt = new LinearInterpolator1D(antecedent, values);
-            Assert.fail("test should had failed");
-        } catch (Exception e) {
-            //ok
+            LinearInterpolator1D.create(x, y);
+            fail("Should not have accepted the x inputs.");
+        } catch (IllegalArgumentException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("x"));
         }
 
-        antecedent = new double[]{1017,525,24,12};
-        values     = new double[]{-43,7,-19,105};
-        MathTransform1D mt = new LinearInterpolator1D(antecedent, values);
+        x = new double[] {1017, 525,  24,  12};
+        y = new double[] {-43,    7, -19, 105};
+        MathTransform1D mt = LinearInterpolator1D.create(x, y);
         try {
             mt.inverse();
-            Assert.fail("test should had failed");
-        } catch (Exception e) {
-            //ok
+            fail("Should not have accepted the inverse that transform.");
+        } catch (NoninvertibleTransformException e) {
+            final String message = e.getMessage();
+            assertFalse(message, message.isEmpty());
         }
-        antecedent = new double[]{1017,525,24,12,45};
-        values     = new double[]{-43,7,-19,105};
+
+        x = new double[] {1017, 525,  24,  12, 45};                     // Mismatched array
length.
+        y = new double[] {-43,    7, -19, 105};
         try {
-            mt = new LinearInterpolator1D(antecedent, values);
-            Assert.fail("test should had failed");
-        } catch (Exception e) {
-            //ok
+            LinearInterpolator1D.create(x, y);
+            fail("Should not have accepted the x inputs.");
+        } catch (IllegalArgumentException e) {
+            final String message = e.getMessage();
+            assertFalse(message, message.isEmpty());
         }
     }
 
     /**
-     * Test MathTransform.
-     *
-     * @param testInvert apply test on invert transform if true else not.
-     * @throws NoninvertibleTransformException
-     * @throws TransformException
-     */
-    private void testMath (boolean testInvert) throws NoninvertibleTransformException, TransformException
{
-        final MathTransform1D math1 = new LinearInterpolator1D(antecedent, values);
-        MathTransform1D mathInvert = null;
-        if (testInvert) mathInvert = math1.inverse();
-        int step = 10;
-        final int l = antecedent.length;
-        for (int i = 0; i < l-1; i++) {
-            final double a0    = antecedent[i];
-            final double v0    = values[i];
-            double pasX        = antecedent[i+1] - a0;
-            double pasY        = values[i+1] - v0;
-            final double deriv = pasY / pasX;
-            pasX /= step;
-            pasY /= step;
-            for (int s = 0; s < step; s++) {
-                //mathtransform
-                final double x = a0 + s*pasX;
-                final double y = math1.transform(x);
-                //derivative
-                assertTrue(y-(v0+s*pasY) <= 1E-9);
-                if (s != step-1) assertTrue(math1.derivative(x) - deriv <= 1E-9);
-                if (testInvert) {
-                    //inverse transform
-                    assertTrue(mathInvert.transform(y)-x <= 1E-9);
-                    //inverse derivative
-                    if (s != step-1) assertTrue(mathInvert.derivative(y)-(1/deriv) <=
1E-9);
-                }
-            }
+     * Transforms point and verifies that the result is consistent with the inverse transform
and the derivative.
+     */
+    private void verifyConsistency(final double min, final double max, final long randomSeed)
throws TransformException {
+        transform = LinearInterpolator1D.create(x, y);
+        tolerance = 1E-10;
+        derivativeDeltas = new double[] {0.1};
+        /*
+         * Convert a x value to y value, then back to x.
+         * This code is provided mostly as a convenience place where to step into with a
debugger.
+         */
+        if (isInverseTransformSupported) {
+            final double xm = (min + max) / 2;
+            final double ym = ((MathTransform1D) transform).transform(xm);
+            assertEquals(xm,  ((MathTransform1D) transform.inverse()).transform(ym), tolerance);
         }
+        /*
+         * The actual test: 100 random values, test all transform methods
+         * (including those working on arrays), verify consistency and derivatives.
+         */
+        verifyInDomain(new double[] {min}, new double[] {max}, new int[] {100}, new Random(randomSeed));
     }
 }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
[UTF-8] Fri Apr  1 22:21:18 2016
@@ -791,6 +791,11 @@ public final class Errors extends Indexe
         public static final short NonLinearUnit_1 = 84;
 
         /**
+         * The “{0}” sequence is not monotonic.
+         */
+        public static final short NonMonotonicSequence_1 = 221;
+
+        /**
          * Axis directions {0} and {1} are not perpendicular.
          */
         public static final short NonPerpendicularDirections_2 = 85;

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
[ISO-8859-1] Fri Apr  1 22:21:18 2016
@@ -164,6 +164,7 @@ NonInvertibleTransform            = Tran
 NonAngularUnit_1                  = \u201c{0}\u201d is not an angular unit.
 NonLinearUnit_1                   = \u201c{0}\u201d is not a linear unit.
 NonLinearUnitConversion_2         = Unit conversion from \u201c{0}\u201d to \u201c{1}\u201d
is non-linear.
+NonMonotonicSequence_1            = The \u201c{0}\u201d sequence is not monotonic.
 NonPerpendicularDirections_2      = Axis directions {0} and {1} are not perpendicular.
 NonScaleUnit_1                    = \u201c{0}\u201d is not a scale unit.
 NonTemporalUnit_1                 = \u201c{0}\u201d is not a time unit.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1737448&r1=1737447&r2=1737448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
[ISO-8859-1] Fri Apr  1 22:21:18 2016
@@ -161,6 +161,7 @@ NonInvertibleTransform            = La t
 NonAngularUnit_1                  = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une unit\u00e9
d\u2019angles.
 NonLinearUnit_1                   = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une unit\u00e9
de longueurs.
 NonLinearUnitConversion_2         = La conversion des unit\u00e9s \u00ab\u202f{0}\u202f\u00bb
vers \u00ab\u202f{1}\u202f\u00bb n\u2019est pas lin\u00e9aire.
+NonMonotonicSequence_1            = La s\u00e9quence \u00ab\u202f{0}\u202f\u00bb n\u2019est
pas monotone.
 NonPerpendicularDirections_2      = Les directions {0} et {1} ne sont pas perpendiculaires.
 NonScaleUnit_1                    = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une unit\u00e9
d\u2019\u00e9chelles.
 NonTemporalUnit_1                 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une unit\u00e9
de temps.




Mime
View raw message