You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2018/01/26 14:38:49 UTC
svn commit: r1822302 - in /sis/branches/JDK8/core/sis-referencing/src:
main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java
test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
Author: desruisseaux
Date: Fri Jan 26 14:38:49 2018
New Revision: 1822302
URL: http://svn.apache.org/viewvc?rev=1822302&view=rev
Log:
Modification in the extrapolation method of LinearInterpolator1D:
keep the same slope than the extremum where extrapolation occurs.
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
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=1822302&r1=1822301&r2=1822302&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java [UTF-8] Fri Jan 26 14:38:49 2018
@@ -39,11 +39,26 @@ import org.apache.sis.util.resources.Err
* <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>
*
+ * <div class="section">Extrapolation</div>
+ * If an input value is outside the expected range of values, this class extrapolates using the
+ * slope defined by the two first points if the requested value is before, or the slope defined
+ * by the two last points if the requested value is after. In other words, extrapolations are
+ * computed using only values at the extremum where extrapolation happen. This rule causes less
+ * surprising behavior when computing a data cube envelope, which may need extrapolation by 0.5
+ * pixel before the first value or after the last value.
+ *
+ * <div class="note"><b>Example:</b>
+ * if a vertical dimension is made of slices at y₀=5, y₁=10, y₂=100 and y₃=250 meters, then linear
+ * interpolation at 0.5 is 7.5 meters and extrapolation at -0.5 is expected to give 2.5 meters.</div>
+ *
* @author Johann Sorel (Geomatys)
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since 0.7
+ * @version 1.0
+ *
+ * @see MathTransforms#interpolate(double[], double[])
+ *
+ * @since 0.7
* @module
*/
final class LinearInterpolator1D extends AbstractMathTransform1D implements Serializable {
@@ -54,15 +69,11 @@ final class LinearInterpolator1D extends
/**
* The sequence values specified at construction time.
+ * Must contain at least 2 values.
*/
private final double[] values;
/**
- * 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.
*/
@@ -72,15 +83,13 @@ final class LinearInterpolator1D extends
* 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.
*
- * <p>This constructor assumes that the {@code values} array have already be clones,
- * so it will not clone it again.</p>
+ * <p>This constructor assumes that the {@code values} array has already been cloned,
+ * so it will not clone it again. That array shall contain at least two values.</p>
*
* @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.
*/
- private LinearInterpolator1D(final double[] values, final double slope) {
+ private LinearInterpolator1D(final double[] values) {
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.
@@ -93,14 +102,15 @@ final class LinearInterpolator1D extends
/**
* 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.
+ * interpolator if the given values form a series with a constant increment. The given array shall
+ * contain at least two values.
*
* @param values a <strong>copy</strong> of the user-provided values. This array may be modified.
*/
private static MathTransform1D create(final double[] values) {
final int n = values.length - 1;
final double offset = values[0];
- double slope = (values[n] - offset) / n;
+ final double slope = (values[n] - offset) / n;
final double as = Math.abs(slope);
/*
* If the increment between values is constant (with a small tolerance factor),
@@ -122,12 +132,11 @@ final class LinearInterpolator1D extends
*/
final boolean isReverted = (slope < 0);
if (isReverted) {
- slope = -slope;
for (i=0; i <= n; i++) {
values[i] = -values[i];
}
}
- MathTransform1D tr = new LinearInterpolator1D(values, slope);
+ MathTransform1D tr = new LinearInterpolator1D(values);
if (isReverted) {
tr = new ConcatenatedTransformDirect1D(tr, LinearTransform1D.NEGATE);
}
@@ -223,17 +232,21 @@ final class LinearInterpolator1D extends
final int n = values.length - 1;
if (i < n) {
x -= i;
- y = values[i] * (1-x) + values[i+1] * x;
- d = values[i+1] - values[i];
+ final double y0 = values[i ];
+ final double y1 = values[i+1];
+ y = y0 * (1-x) + y1 * x;
+ d = y1 - y0;
} else {
// x is after the last available value.
- y = (x - n) * slope + values[n];
- d = slope;
+ final double y1 = values[n];
+ d = y1 - values[n-1];
+ y = (x - n) * d + y1;
}
} else {
// x is before the first available value.
- y = x * slope + values[0];
- d = slope;
+ final double y0 = values[0];
+ d = values[1] - y0;
+ y = x * d + y0;
}
if (dstPts != null) {
dstPts[dstOff] = y;
@@ -256,11 +269,13 @@ final class LinearInterpolator1D extends
return values[i] * (1-x) + values[i+1] * x;
} else {
// x is after the last available value.
- return (x - n) * slope + values[n];
+ final double y1 = values[n];
+ return (x - n) * (y1 - values[n-1]) + y1;
}
} else {
// x is before the first available value.
- return x * slope + values[0];
+ final double y0 = values[0];
+ return x * (values[1] - y0) + y0;
}
}
@@ -271,13 +286,8 @@ final class LinearInterpolator1D extends
*/
@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 slope;
+ final int i = Math.max(0, Math.min(values.length - 2, (int) x));
+ return values[i+1] - values[i];
}
/**
@@ -320,7 +330,8 @@ final class LinearInterpolator1D extends
int i = Arrays.binarySearch(values, y);
if (i >= 0) {
x = i;
- d = (i >= 1 && i < values.length) ? (values[i] - values[i-1]) : slope;
+ i = Math.max(1, Math.min(values.length - 1, i));
+ d = values[i] - values[i-1];
} else {
i = ~i;
if (i >= 1) {
@@ -330,11 +341,13 @@ final class LinearInterpolator1D extends
} else {
// y is after the last available value.
final int n = values.length - 1;
- x = (y - values[n]) / (d = slope) + n;
+ final double y1 = values[n];
+ x = (y - y1) / (d = y1 - values[n-1]) + n;
}
} else {
// y is before the first available value.
- x = (y - values[0]) / (d = slope);
+ final double y0 = values[0];
+ x = (y - y0) / (d = values[1] - y0);
}
}
if (dstPts != null) {
@@ -361,11 +374,13 @@ final class LinearInterpolator1D extends
} else {
// y is after the last available value.
final int n = values.length - 1;
- return (y - values[n]) / slope + n;
+ final double y1 = values[n];
+ return (y - y1) / (y1 - values[n-1]) + n;
}
} else {
// y is before the first available value.
- return (y - values[0]) / slope;
+ final double y0 = values[0];
+ return (y - y0) / (values[1] - y0);
}
}
}
@@ -380,13 +395,8 @@ final class LinearInterpolator1D extends
if (i < 0) {
i = ~i;
}
- final double d;
- if (i >= 1 && i < values.length) {
- d = (values[i] - values[i-1]);
- } else {
- d = slope;
- }
- return 1 / d;
+ i = Math.max(1, Math.min(values.length - 1, i));
+ return 1 / (values[i] - values[i-1]);
}
}
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=1822302&r1=1822301&r2=1822302&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java [UTF-8] Fri Jan 26 14:38:49 2018
@@ -21,6 +21,7 @@ import org.opengis.referencing.operation
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.test.referencing.TransformTestCase;
+import org.apache.sis.test.DependsOnMethod;
import org.junit.Test;
import static org.opengis.test.Assert.*;
@@ -31,7 +32,7 @@ import static org.opengis.test.Assert.*;
*
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys).
- * @version 0.7
+ * @version 1.0
* @since 0.7
* @module
*/
@@ -238,4 +239,25 @@ public final strictfp class LinearInterp
*/
verifyInDomain(new double[] {min}, new double[] {max}, new int[] {100}, new Random(randomSeed));
}
+
+ /**
+ * Tests input values outside the expected range.
+ * A few values inside ranges are also tested as a safety.
+ *
+ * @throws TransformException if an error occurred while testing a value.
+ */
+ @Test
+ @DependsOnMethod("testIndicesToIncreasingValues")
+ public void testExtrapolations() throws TransformException {
+ values = new double[] {5, 10, 100, 250};
+ transform = LinearInterpolator1D.create(preimage, values);
+ derivativeDeltas = new double[] {0.1};
+ verifyTransform(new double[] {0, 1, 0.5, -0.5, -1, -2, 3, 3.5, 4, 5}, // Values to transform.
+ new double[] {5, 10, 7.5, 2.5, 0, -5, 250, 325, 400, 550}); // Expected results.
+
+ verifyConsistency(0f, 1f, 0.5f, -0.5f, -1f, -2f, 3f, 3.5f, 4f, 5f);
+ verifyDerivative(0.25); // Interpolation (verified by safety)
+ verifyDerivative(-8); // Extrapolation
+ verifyDerivative( 8);
+ }
}