sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1787441 - in /sis/branches/JDK8/core: sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ sis-utility/src/main/java/org/apache/sis/math/
Date Fri, 17 Mar 2017 16:32:14 GMT
Author: desruisseaux
Date: Fri Mar 17 16:32:14 2017
New Revision: 1787441

URL: http://svn.apache.org/viewvc?rev=1787441&view=rev
Log:
Add tests about setting the target coordinates of LinearTransformBuilder when the source coordinates
are on a grid.

Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Line.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java?rev=1787441&r1=1787440&r2=1787441&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
[UTF-8] Fri Mar 17 16:32:14 2017
@@ -33,6 +33,7 @@ import org.apache.sis.referencing.operat
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 
@@ -48,13 +49,6 @@ import org.apache.sis.util.Debug;
  * <p>The transform coefficients are determined using a <cite>least squares</cite>
estimation method,
  * with the assumption that source positions are exact and all the uncertainty is in the
target positions.</p>
  *
- * <div class="note"><b>Implementation note:</b>
- * The quantity that current implementation tries to minimize is not strictly the squared
Euclidian distance.
- * The current implementation rather processes each <em>target</em> dimension
independently, which may not give
- * the same result than if we tried to minimize the squared Euclidian distances by taking
all target dimensions
- * in account together. This algorithm may change in future SIS versions.
- * </div>
- *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
  * @version 0.8
@@ -105,6 +99,8 @@ public class LinearTransformBuilder {
     /**
      * Number of valid positions in the {@link #sources} or {@link #targets} arrays.
      * Note that the "valid" positions may contain {@link Double#NaN} ordinate values.
+     * This field is only indicative if this {@code LinearTransformBuilder} instance
+     * has been created by {@link #LinearTransformBuilder(int...)}.
      */
     private int numPoints;
 
@@ -125,7 +121,7 @@ public class LinearTransformBuilder {
      *
      * <div class="note"><b>Tip:</b>
      * if the source coordinates are grid indices, then
-     * the {@link #LinearTransformBuilder(int, int)} constructor will create a more efficient
builder.
+     * the {@link #LinearTransformBuilder(int...)} constructor will create a more efficient
builder.
      * </div>
      */
     public LinearTransformBuilder() {
@@ -171,30 +167,117 @@ public class LinearTransformBuilder {
     }
 
     /**
-     * Returns the offset where to store a target position for the given source position.
-     * This method should be invoked only when this {@code LinearTransformBuilder} has
-     * been constructed for a grid of known size.
+     * Allocates memory for a builder created for source positions distributed on a grid.
+     * All target values need to be initialized to NaN because we can not rely on {@link
#numPoints}.
+     *
+     * <p>If this builder has been created for randomly distributed source points,
then the allocation
+     * should rather be performed as below:</p>
+     *
+     * {@preformat java
+     *    sources = new double[srcDim][capacity];
+     *    targets = new double[tgtDim][capacity];
+     * }
+     */
+    private void allocate(final int tgtDim) {
+        targets = new double[tgtDim][gridLength];
+        for (final double[] r : targets) {
+            Arrays.fill(r, Double.NaN);
+        }
+    }
+
+    /**
+     * Resize all the given arrays to the given capacity. This method should be invoked only
for
+     * {@code LinearTransformBuilder} instances created for randomly distributed source positions.
+     */
+    private static void resize(double[][] data, final int capacity) {
+        for (int i=0; i<data.length; i++) {
+            data[i] = ArraysExt.resize(data[i], capacity);
+        }
+    }
+
+    /**
+     * Returns the offset of the given source grid coordinate, or -1 if none. The algorithm
implemented in this
+     * method is inefficient, but should rarely be used. This is only a fallback when {@link
#flatIndex(int[])}
+     * can not be used.
+     */
+    private int search(final int[] source) {
+        assert gridSize == null;         // This method should not be invoked for points
distributed on a grid.
+search: for (int j=0; j<numPoints; j++) {
+            for (int i=0; i<source.length; i++) {
+                if (source[i] != sources[i][j]) {
+                    continue search;                            // Search another position
for the same source.
+                }
+            }
+            return j;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the offset where to store a target position for the given source position
in the flattened array.
+     * This method should be invoked only when this {@code LinearTransformBuilder} has been
created for a grid
+     * of known size. Caller must have verified the array length before to invoke this method.
      *
      * @throws IllegalArgumentException if an ordinate value is illegal.
      */
-    private int offset(final DirectPosition src) {
-        int offset = 1;
-        for (int j = gridSize.length; j != 0;) {
-            final int s = gridSize[--j];
-            final double ordinate = src.getOrdinate(j);
-            final int i = (int) ordinate;
-            if (i != ordinate) {
+    private int flatIndex(final int[] source) {
+        assert sources == null;               // This method should not be invoked for randomly
distributed points.
+        int offset = 0;
+        for (int i = gridSize.length; i != 0;) {
+            final int size = gridSize[--i];
+            final int index = source[i];
+            if (index < 0 || index >= size) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4,
"source", 0, size-1, index));
+            }
+            offset = offset * size + index;
+        }
+        return offset;
+    }
+
+    /**
+     * Returns the index where to store a target position for the given source position in
the flattened array.
+     * This method should be invoked only when this {@code LinearTransformBuilder} has been
created for a grid
+     * of known size. Callers must have verified the position dimension before to invoke
this method.
+     *
+     * @throws IllegalArgumentException if an ordinate value is illegal.
+     */
+    private int flatIndex(final DirectPosition source) {
+        assert sources == null;               // This method should not be invoked for randomly
distributed points.
+        int offset = 0;
+        for (int i = gridSize.length; i != 0;) {
+            final int size = gridSize[--i];
+            final double ordinate = source.getOrdinate(i);
+            final int index = (int) ordinate;
+            if (index != ordinate) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.NotAnInteger_1,
ordinate));
             }
-            if (i < 0 || i >= s) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4,
"source", 0, s-1, i));
+            if (index < 0 || index >= size) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4,
"source", 0, size-1, index));
             }
-            offset = offset * s + i;
+            offset = offset * size + index;
         }
         return offset;
     }
 
     /**
+     * Verifies that the given number of dimensions is equal to the expected value.
+     * No verification are done if the source point is the first point of randomly distributed
points.
+     */
+    private void verifySourceDimension(final int actual) {
+        final int expected;
+        if (gridSize != null) {
+            expected = gridSize.length;
+        } else if (sources != null) {
+            expected = sources.length;
+        } else {
+            return;
+        }
+        if (actual != expected) {
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
"source", expected, actual));
+        }
+    }
+
+    /**
      * Builds the exception message for an unexpected position dimension. This method assumes
      * that positions are stored in this builder as they are read from user-provided collection.
      */
@@ -210,9 +293,10 @@ public class LinearTransformBuilder {
     }
 
     /**
-     * Sets the source and target positions, overwriting any previous setting. The source
positions are the keys in
+     * Sets all matching control point pairs, overwriting any previous setting. The source
positions are the keys in
      * the given map, and the target positions are the associated values in the map. The
map should not contain two
-     * entries with the same source position. Null positions are silently ignored.
+     * entries with the same source position. Coordinate reference systems are ignored.
+     * Null positions are silently ignored.
      *
      * <p>All source positions shall have the same number of dimensions (the <cite>source
dimension</cite>),
      * and all target positions shall have the same number of dimensions (the <cite>target
dimension</cite>).
@@ -228,11 +312,13 @@ public class LinearTransformBuilder {
      *
      * @param  sourceToTarget  a map of source positions to target positions.
      *         Source positions are assumed precise and target positions are assumed uncertain.
+     * @throws IllegalArgumentException if this builder has been {@linkplain #LinearTransformBuilder(int...)
+     *         created for a grid} but some source ordinates are not indices in that grid.
      * @throws MismatchedDimensionException if some positions do not have the expected number
of dimensions.
      *
      * @since 0.8
      */
-    public void setPositions(final Map<? extends Position, ? extends Position> sourceToTarget)
+    public void setControlPoints(final Map<? extends Position, ? extends Position>
sourceToTarget)
             throws MismatchedDimensionException
     {
         ArgumentChecks.ensureNonNull("sourceToTarget", sourceToTarget);
@@ -262,15 +348,12 @@ public class LinearTransformBuilder {
                     if (srcDim <= 0) {
                         throw new MismatchedDimensionException(mismatchedDimension("source",
2, srcDim));
                     }
-                    final int length = sourceToTarget.size();
-                    sources = new double[srcDim][length];
-                    targets = new double[tgtDim][length];
+                    final int capacity = sourceToTarget.size();
+                    sources = new double[srcDim][capacity];
+                    targets = new double[tgtDim][capacity];
                 } else {
-                    srcDim  = gridSize.length;
-                    targets = new double[tgtDim][gridLength];
-                    for (final double[] r : targets) {
-                        Arrays.fill(r, Double.NaN);
-                    }
+                    srcDim = gridSize.length;
+                    allocate(tgtDim);
                 }
             }
             /*
@@ -283,7 +366,7 @@ public class LinearTransformBuilder {
             if ((d = tgt.getDimension()) != tgtDim) throw new MismatchedDimensionException(mismatchedDimension("target",
tgtDim, d));
             int index;
             if (gridSize != null) {
-                index = offset(src);
+                index = flatIndex(src);
             } else {
                 index = numPoints;
                 for (int i=0; i<srcDim; i++) {
@@ -298,6 +381,111 @@ public class LinearTransformBuilder {
     }
 
     /**
+     * Sets a single matching control point pair. Source position is assumed precise and
target position is assumed uncertain.
+     * If the given source position was already associated with another target position,
then the old target position is discarded.
+     *
+     * <div class="note"><b>Performance note:</b>
+     * current implementation is efficient for builders {@linkplain #LinearTransformBuilder(int...)
created for a grid}
+     * but inefficient for builders {@linkplain #LinearTransformBuilder() created for randomly
distributed points}.
+     * In the later case, the {@link #setControlPoints(Map)} method is a more efficient alternative.</div>
+     *
+     * @param  source  the source coordinates. If this builder has been created with the
{@link #LinearTransformBuilder(int...)} constructor,
+     *                 then for every index <var>i</var> the {@code source[i]}
value shall be in the [0 … {@code gridSize[i]}-1] range inclusive.
+     *                 If this builder has been created with the {@link #LinearTransformBuilder()}
constructor, then no constraint apply.
+     * @param  target  the target coordinates, assumed uncertain.
+     * @throws IllegalArgumentException if this builder has been {@linkplain #LinearTransformBuilder(int...)
created for a grid}
+     *         but some source ordinates are out of index range.
+     * @throws MismatchedDimensionException if the source or target position does not have
the expected number of dimensions.
+     *
+     * @since 0.8
+     */
+    public void setControlPoint(final int[] source, final double[] target) {
+        ArgumentChecks.ensureNonNull("source", source);
+        ArgumentChecks.ensureNonNull("target", target);
+        verifySourceDimension(source.length);
+        final int tgtDim = target.length;
+        if (targets != null && tgtDim != targets.length) {
+            throw new MismatchedDimensionException(Errors.format(
+                    Errors.Keys.MismatchedDimension_3, "target", targets.length, tgtDim));
+        }
+        int index;
+        if (gridSize != null) {
+            index = flatIndex(source);        // Invoked first for validating argument before
to allocate arrays.
+            if (targets == null) {
+                allocate(tgtDim);
+            }
+        } else {
+            /*
+             * Case of randomly distributed points. Algorithm used below is inefficient,
but Javadoc
+             * warns the user that (s)he should use setControlPoints(Map) instead in such
case.
+             */
+            final int srcDim = source.length;
+            if (targets == null) {
+                targets = new double[tgtDim][20];                   // Arbitrary initial
capacity of 20 points.
+                sources = new double[srcDim][20];
+            }
+            index = search(source);
+            if (index < 0) {
+                index = numPoints++;
+                if (numPoints >= targets[0].length) {
+                    final int n = Math.multiplyExact(numPoints, 2);
+                    resize(sources, n);
+                    resize(targets, n);
+                }
+            }
+            for (int i=0; i<srcDim; i++) {
+                sources[i][index] = source[i];
+            }
+        }
+        for (int i=0; i<tgtDim; i++) {
+            targets[i][index] = target[i];
+        }
+    }
+
+    /**
+     * Returns a single target coordinate for the given source coordinate, or {@code null}
if none.
+     * This method can be used for retrieving points set by previous calls to
+     * {@link #setControlPoint(int[], double[])} or {@link #setControlPoints(Map)}.
+     *
+     * <div class="note"><b>Performance note:</b>
+     * current implementation is efficient for builders {@linkplain #LinearTransformBuilder(int...)
created for a grid}
+     * but inefficient for builders {@linkplain #LinearTransformBuilder() created for randomly
distributed points}.</div>
+     *
+     * @param  source  the source coordinates. If this builder has been created with the
{@link #LinearTransformBuilder(int...)} constructor,
+     *                 then for every index <var>i</var> the {@code source[i]}
value shall be in the [0 … {@code gridSize[i]}-1] range inclusive.
+     *                 If this builder has been created with the {@link #LinearTransformBuilder()}
constructor, then no constraint apply.
+     * @return the target coordinates associated to the given source, or {@code null} if
none.
+     * @throws IllegalArgumentException if this builder has been {@linkplain #LinearTransformBuilder(int...)
created for a grid}
+     *         but some source ordinates are out of index range.
+     * @throws MismatchedDimensionException if the source position does not have the expected
number of dimensions.
+     *
+     * @since 0.8
+     */
+    public double[] getControlPoint(final int[] source) {
+        processPendings();
+        ArgumentChecks.ensureNonNull("source", source);
+        verifySourceDimension(source.length);
+        if (targets == null) {
+            return null;
+        }
+        final int index;
+        if (gridSize != null) {
+            index = flatIndex(source);
+        } else {
+            index = search(source);
+            if (index < 0) {
+                return null;
+            }
+        }
+        boolean isNaN = true;
+        final double[] target = new double[targets.length];
+        for (int i=0; i<target.length; i++) {
+            isNaN &= Double.isNaN(target[i] = targets[i][index]);
+        }
+        return isNaN ? null : target;
+    }
+
+    /**
      * Sets the source points, overwriting any previous setting. The number of source points
will need to be the same
      * than the number of {@linkplain #setTargetPoints target points} when the {@link #create()}
method will be invoked.
      * In current Apache SIS implementation, the source points must be one or two-dimensional.
@@ -314,7 +502,7 @@ public class LinearTransformBuilder {
      * @param  points  the source points, assumed precise.
      * @throws MismatchedDimensionException if at least one point does not have the expected
number of dimensions.
      *
-     * @deprecated Replaced by {@link #setPositions(Map)}.
+     * @deprecated Replaced by {@link #setControlPoints(Map)}.
      */
     @Deprecated
     public void setSourcePoints(final DirectPosition... points) throws MismatchedDimensionException
{
@@ -336,7 +524,7 @@ public class LinearTransformBuilder {
      * @param  points  the target points, assumed uncertain.
      * @throws MismatchedDimensionException if not all points have the same number of dimensions.
      *
-     * @deprecated Replaced by {@link #setPositions(Map)}.
+     * @deprecated Replaced by {@link #setControlPoints(Map)}.
      */
     @Deprecated
     public void setTargetPoints(final DirectPosition... points) throws MismatchedDimensionException
{
@@ -367,14 +555,13 @@ public class LinearTransformBuilder {
             for (int i=0; i<length; i++) {
                 sourceToTarget.put(pendingSources[i], pendingTargets[i]);
             }
-            setPositions(sourceToTarget);
+            setControlPoints(sourceToTarget);
         }
     }
 
     /**
      * Creates a linear transform approximation from the source positions to the target positions.
-     * This method assumes that source positions are precise and that all uncertainties are
in the
-     * target positions.
+     * This method assumes that source positions are precise and that all uncertainty is
in the target positions.
      *
      * @return the fitted linear transform.
      * @throws IllegalStateException if the source or target points have not be specified
@@ -399,7 +586,7 @@ public class LinearTransformBuilder {
                     case 1: {
                         final Line line = new Line();
                         if (sources != null) {
-                            c = line.fit(sources[0], targets[j]);
+                            c = line.fit(vector(sources[0]), vector(targets[j]));
                         } else {
                             c = line.fit(Vector.createSequence(0, 1, gridSize[0]),
                                          Vector.create(targets[j], false));
@@ -411,7 +598,7 @@ public class LinearTransformBuilder {
                     case 2: {
                         final Plane plan = new Plane();
                         if (sources != null) {
-                            c = plan.fit(sources[0], sources[1], targets[j]);
+                            c = plan.fit(vector(sources[0]), vector(sources[1]), vector(targets[j]));
                         } else {
                             c = plan.fit(gridSize[0], gridSize[1], Vector.create(targets[j],
false));
                         }
@@ -432,6 +619,16 @@ public class LinearTransformBuilder {
     }
 
     /**
+     * Wraps the given array in a vector of length {@link #numPoints}. This method should
be
+     * invoked only when this builder has been created by {@link #LinearTransformBuilder()}.
+     * This can be identified by {@code sources != null} or {@code gridSize == null}.
+     */
+    private Vector vector(final double[] data) {
+        assert gridSize == null;
+        return Vector.create(data, false).subList(0, numPoints);
+    }
+
+    /**
      * Returns the correlation coefficients of the last transform created by {@link #create()},
      * or {@code null} if none. If non-null, the array length is equals to the number of
target
      * dimensions.

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java?rev=1787441&r1=1787440&r2=1787441&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
[UTF-8] Fri Mar 17 16:32:14 2017
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.referencing.operation.builder;
 
+import java.util.Map;
+import java.util.HashMap;
 import java.util.Random;
 import java.awt.geom.AffineTransform;
 import org.opengis.referencing.operation.Matrix;
@@ -34,7 +36,7 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.8
  * @module
  */
 public final strictfp class LinearTransformBuilderTest extends TestCase {
@@ -44,12 +46,15 @@ public final strictfp class LinearTransf
     @Test
     public void testMinimalist1D() {
         final LinearTransformBuilder builder = new LinearTransformBuilder();
-        builder.setSourcePoints(
-                new DirectPosition1D(1),
-                new DirectPosition1D(2));
-        builder.setTargetPoints(
-                new DirectPosition1D(1),
-                new DirectPosition1D(3));
+        final Map<DirectPosition1D,DirectPosition1D> pos = new HashMap<>(4);
+        assertNull(pos.put(new DirectPosition1D(1), new DirectPosition1D(1)));
+        assertNull(pos.put(new DirectPosition1D(2), new DirectPosition1D(3)));
+        builder.setControlPoints(pos);
+
+        assertArrayEquals(new double[] {1}, builder.getControlPoint(new int[] {1}), STRICT);
+        assertArrayEquals(new double[] {3}, builder.getControlPoint(new int[] {2}), STRICT);
+        assertNull(                         builder.getControlPoint(new int[] {3}));
+
         final Matrix m = builder.create().getMatrix();
         assertEquals("m₀₀",  2, m.getElement(0, 0), STRICT);
         assertEquals("m₀₁", -1, m.getElement(0, 1), STRICT);
@@ -73,6 +78,12 @@ public final strictfp class LinearTransf
                 new DirectPosition2D(3, 2),
                 new DirectPosition2D(3, 5),
                 new DirectPosition2D(5, 5));
+
+        assertArrayEquals(new double[] {3, 2}, builder.getControlPoint(new int[] {1, 1}),
STRICT);
+        assertArrayEquals(new double[] {3, 5}, builder.getControlPoint(new int[] {1, 2}),
STRICT);
+        assertArrayEquals(new double[] {5, 5}, builder.getControlPoint(new int[] {2, 2}),
STRICT);
+        assertNull(                            builder.getControlPoint(new int[] {2, 1}));
+
         final Matrix m = builder.create().getMatrix();
 
         // First row (x)
@@ -89,6 +100,83 @@ public final strictfp class LinearTransf
     }
 
     /**
+     * Tests a two-dimensional case where sources coordinates are explicitely given.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testExplicitSource2D() {
+        testSetAllPoints(new LinearTransformBuilder());
+        testSetEachPoint(new LinearTransformBuilder());
+    }
+
+    /**
+     * Same test than {@link #testExplicitSource2D()}, but using the
+     * {@link LinearTransformBuilder#LinearTransformBuilder(int...)} constructor.
+     *
+     * @since 0.8
+     */
+    @Test
+    @DependsOnMethod("testExplicitSource2D")
+    public void testImplicitSource2D() {
+        testSetAllPoints(new LinearTransformBuilder(2, 3));
+        testSetEachPoint(new LinearTransformBuilder(2, 3));
+    }
+
+    /**
+     * Execution of {@link #testExplicitSource2D()} and {@link #testImplicitSource2D()}
+     * where all control points are specified by a map.
+     */
+    private void testSetAllPoints(final LinearTransformBuilder builder) {
+        final Map<DirectPosition2D,DirectPosition2D> pos = new HashMap<>(8);
+        assertNull(pos.put(new DirectPosition2D(0, 0), new DirectPosition2D(3, 9)));
+        assertNull(pos.put(new DirectPosition2D(0, 1), new DirectPosition2D(4, 7)));
+        assertNull(pos.put(new DirectPosition2D(0, 2), new DirectPosition2D(6, 6)));
+        assertNull(pos.put(new DirectPosition2D(1, 0), new DirectPosition2D(4, 8)));
+        assertNull(pos.put(new DirectPosition2D(1, 1), new DirectPosition2D(5, 4)));
+        assertNull(pos.put(new DirectPosition2D(1, 2), new DirectPosition2D(8, 2)));
+        builder.setControlPoints(pos);
+        verify(builder);
+    }
+
+    /**
+     * Execution of {@link #testExplicitSource2D()} and {@link #testImplicitSource2D()}
+     * where all control points are specified one-by-one.
+     */
+    private void testSetEachPoint(final LinearTransformBuilder builder) {
+        builder.setControlPoint(new int[] {0, 0}, new double[] {3, 9});
+        builder.setControlPoint(new int[] {0, 1}, new double[] {4, 7});
+        builder.setControlPoint(new int[] {0, 2}, new double[] {6, 6});
+        builder.setControlPoint(new int[] {1, 0}, new double[] {4, 8});
+        builder.setControlPoint(new int[] {1, 1}, new double[] {5, 4});
+        builder.setControlPoint(new int[] {1, 2}, new double[] {8, 2});
+        verify(builder);
+    }
+
+    /**
+     * Verifies the transform created by {@link #testExplicitSource2D()} and {@link #testImplicitSource2D()}.
+     */
+    private void verify(final LinearTransformBuilder builder) {
+        final Matrix m = builder.create().getMatrix();
+
+        assertArrayEquals(new double[] {3, 9}, builder.getControlPoint(new int[] {0, 0}),
STRICT);
+        assertArrayEquals(new double[] {4, 7}, builder.getControlPoint(new int[] {0, 1}),
STRICT);
+        assertArrayEquals(new double[] {6, 6}, builder.getControlPoint(new int[] {0, 2}),
STRICT);
+        assertArrayEquals(new double[] {4, 8}, builder.getControlPoint(new int[] {1, 0}),
STRICT);
+        assertArrayEquals(new double[] {5, 4}, builder.getControlPoint(new int[] {1, 1}),
STRICT);
+        assertArrayEquals(new double[] {8, 2}, builder.getControlPoint(new int[] {1, 2}),
STRICT);
+
+        assertEquals("m₀₀",  16 / 12d, m.getElement(0, 0), STRICT);        // First row
(x)
+        assertEquals("m₀₁",  21 / 12d, m.getElement(0, 1), STRICT);
+        assertEquals("m₀₂",  31 / 12d, m.getElement(0, 2), STRICT);
+        assertEquals("m₁₀", -32 / 12d, m.getElement(1, 0), STRICT);        // Second
row (y)
+        assertEquals("m₁₁", -27 / 12d, m.getElement(1, 1), STRICT);
+        assertEquals("m₁₂", 115 / 12d, m.getElement(1, 2), STRICT);
+
+        assertArrayEquals("correlation", new double[] {0.9656, 0.9536}, builder.correlation(),
0.0001);
+    }
+
+    /**
      * Tests with a random number of points with an exact solution expected.
      */
     @Test
@@ -159,23 +247,20 @@ public final strictfp class LinearTransf
     {
         final double scale  = rd.nextDouble() * 30 - 12;
         final double offset = rd.nextDouble() * 10 - 4;
-        final DirectPosition1D[] sources = new DirectPosition1D[numPts];
-        final DirectPosition1D[] targets = new DirectPosition1D[numPts];
+        final Map<DirectPosition1D,DirectPosition1D> pos = new HashMap<>(numPts);
         for (int i=0; i<numPts; i++) {
             final DirectPosition1D src = new DirectPosition1D(rd.nextDouble() * 100 - 50);
             final DirectPosition1D tgt = new DirectPosition1D(src.ordinate * scale + offset);
             if (addErrors) {
                 tgt.ordinate += rd.nextDouble() * 10 - 5;
             }
-            sources[i] = src;
-            targets[i] = tgt;
+            assertNull(pos.put(src, tgt));
         }
         /*
          * Create the fitted transform to test.
          */
         final LinearTransformBuilder builder = new LinearTransformBuilder();
-        builder.setSourcePoints(sources);
-        builder.setTargetPoints(targets);
+        builder.setControlPoints(pos);
         final Matrix m = builder.create().getMatrix();
         assertEquals("m₀₀", scale,  m.getElement(0, 0), scaleTolerance);
         assertEquals("m₀₁", offset, m.getElement(0, 1), translationTolerance);
@@ -200,8 +285,7 @@ public final strictfp class LinearTransf
                 rd.nextDouble() * (2 * StrictMath.PI),  // Rotation angle
                 rd.nextDouble() * 30 - 12,              // Center X
                 rd.nextDouble() * 10 - 8);              // Center Y
-        final DirectPosition2D[] sources = new DirectPosition2D[numPts];
-        final DirectPosition2D[] targets = new DirectPosition2D[numPts];
+        final Map<DirectPosition2D,DirectPosition2D> pos = new HashMap<>(numPts);
         for (int i=0; i<numPts; i++) {
             final DirectPosition2D src = new DirectPosition2D(rd.nextDouble() * 100 - 50,
rd.nextDouble() * 200 - 75);
             final DirectPosition2D tgt = new DirectPosition2D();
@@ -210,15 +294,13 @@ public final strictfp class LinearTransf
                 tgt.x += rd.nextDouble() * 10 - 5;
                 tgt.y += rd.nextDouble() * 10 - 5;
             }
-            sources[i] = src;
-            targets[i] = tgt;
+            assertNull(pos.put(src, tgt));
         }
         /*
          * Create the fitted transform to test.
          */
         final LinearTransformBuilder builder = new LinearTransformBuilder();
-        builder.setSourcePoints(sources);
-        builder.setTargetPoints(targets);
+        builder.setControlPoints(pos);
         final Matrix m = builder.create().getMatrix();
         /*
          * Compare the coefficients with the reference implementation.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Line.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Line.java?rev=1787441&r1=1787440&r2=1787441&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Line.java [UTF-8]
(original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Line.java [UTF-8]
Fri Mar 17 16:32:14 2017
@@ -220,8 +220,8 @@ public class Line implements Cloneable,
     /**
      * Given a set of data points <var>x</var>[0 … <var>n</var>-1],
<var>y</var>[0 … <var>n</var>-1],
      * fits them to a straight line <var>y</var> = <var>slope</var>⋅<var>x</var>
+ <var>y₀</var> in a
-     * least-squares senses. This method assume that the <var>x</var> values
are precise and all uncertainty
-     * is in <var>y</var>.
+     * least-squares senses.
+     * This method assumes that the <var>x</var> values are precise and all uncertainty
is in <var>y</var>.
      *
      * <p>The default implementation delegates to {@link #fit(Vector, Vector)}.</p>
      *
@@ -240,8 +240,8 @@ public class Line implements Cloneable,
     /**
      * Given a set of data points <var>x</var>[0 … <var>n</var>-1],
<var>y</var>[0 … <var>n</var>-1],
      * fits them to a straight line <var>y</var> = <var>slope</var>⋅<var>x</var>
+ <var>y₀</var> in a
-     * least-squares senses. This method assume that the <var>x</var> values
are precise and all uncertainty
-     * is in <var>y</var>.
+     * least-squares senses.
+     * This method assumes that the <var>x</var> values are precise and all uncertainty
is in <var>y</var>.
      *
      * <p>The default implementation delegates to {@link #fit(Iterable)}.</p>
      *
@@ -260,12 +260,11 @@ public class Line implements Cloneable,
     }
 
     /**
-     * Given a sequence of points, fits them to a straight line <var>y</var>
= <var>slope</var>⋅<var>x</var> +
-     * <var>y₀</var> in a least-squares senses. This method assume that the
<var>x</var> values are precise and
-     * all uncertainty is in <var>y</var>.
-     *
-     * <p>Points shall be two dimensional with ordinate values in the (<var>x</var>,<var>y</var>)
order.
-     * {@link Double#NaN} ordinate values are ignored.</p>
+     * Given a sequence of points, fits them to a straight line
+     * <var>y</var> = <var>slope</var>⋅<var>x</var>
+ <var>y₀</var> in a least-squares senses.
+     * Points shall be two dimensional with ordinate values in the (<var>x</var>,<var>y</var>)
order.
+     * This method assumes that the <var>x</var> values are precise and all uncertainty
is in <var>y</var>.
+     * {@link Double#NaN} ordinate values are ignored.
      *
      * @param  points  the two-dimensional points.
      * @return estimation of the correlation coefficient. The closer this coefficient is
to +1 or -1, the better the fit.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java?rev=1787441&r1=1787440&r2=1787441&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java [UTF-8]
(original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java [UTF-8]
Fri Mar 17 16:32:14 2017
@@ -261,26 +261,28 @@ public class Plane implements Cloneable,
      * is equivalent to invoking {@link #fit(Vector, Vector, Vector)} where all vectors have
a length of
      * {@code nx} × {@code ny} and the <var>x</var> and <var>y</var>
vectors have the following content:
      *
+     * <blockquote>
      * <table class="compact" summary="x and y vector content">
      *   <tr>
      *     <th><var>x</var> vector</th>
      *     <th><var>y</var> vector</th>
      *   </tr><tr>
-     *     <td>{@preformat math
-     *          0 1 2 3 4 5 … n<sub>x</sub>-1
-     *          0 1 2 3 4 5 … n<sub>x</sub>-1
-     *          0 1 2 3 4 5 … n<sub>x</sub>-1
-     *          …
-     *          0 1 2 3 4 5 … n<sub>x</sub>-1
-     *     }</td>
-     *     <td>{@preformat math
-     *          0 0 0 0 0 0 … 0
-     *          1 1 1 1 1 1 … 1
-     *          2 2 2 2 2 2 … 2
-     *          n<sub>y</sub>-1 n<sub>y</sub>-1 n<sub>y</sub>-1
… n<sub>y</sub>-1
-     *     }</td>
+     *     <td>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *       …<br>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *     </td><td>
+     *       0 0 0 0 0 0 … 0<br>
+     *       1 1 1 1 1 1 … 1<br>
+     *       2 2 2 2 2 2 … 2<br>
+     *       …<br>
+     *       n<sub>y</sub>-1 n<sub>y</sub>-1 n<sub>y</sub>-1
… n<sub>y</sub>-1<br>
+     *     </td>
      *   </tr>
      * </table>
+     * </blockquote>
      *
      * This method uses a linear regression in the least-square sense, with the assumption
that
      * the (<var>x</var>,<var>y</var>) values are precise and all
uncertainty is in <var>z</var>.
@@ -405,18 +407,19 @@ public class Plane implements Cloneable,
              *
              * Note that for exclusive upper bound, we need to replace n by n-1 in above
formulas.
              */
-            int i = 0, n = 0;
+            int n = 0;
             for (int y=0; y<ny; y++) {
                 for (int x=0; x<nx; x++) {
-                    final double z = vz.doubleValue(i++);
-                    if (!Double.isNaN(z)) {
-                        zx.setToProduct(z, x);
-                        zy.setToProduct(z, y);
-                        sum_z .add(z );
-                        sum_zx.add(zx);
-                        sum_zy.add(zy);
-                        n++;
+                    final double z = vz.doubleValue(n);
+                    if (Double.isNaN(z)) {
+                        throw new IllegalArgumentException(Errors.format(Errors.Keys.NotANumber_1,
"z[" + n + ']'));
                     }
+                    zx.setToProduct(z, x);
+                    zy.setToProduct(z, y);
+                    sum_z .add(z );
+                    sum_zx.add(zx);
+                    sum_zy.add(zy);
+                    n++;
                 }
             }
             sum_x .value = n/2d;  sum_x .multiply(nx-1,   0);                     // Division
by 2 is exact.



Mime
View raw message