This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new a685c8a  When computing a linear regression for 
LocalizationGridBuilder purpose, minimize the errors on the grid indices 
instead than the errors on the geospatial coordinates. This is because errors 
on geospatial coordinates do not hurt, since they are corrected by the 
localization grid in the second step of the coordinate transformations. However 
errors on grid indices are problematic during the inverse transformation, since 
they can lead to "no convergence" errors.
a685c8a is described below

commit a685c8a1bf0b412a544713f1ceda9749a995a5d6
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Mar 30 18:16:04 2019 +0100

    When computing a linear regression for LocalizationGridBuilder purpose, 
minimize the errors on the grid indices instead than the errors on the 
geospatial coordinates.
    This is because errors on geospatial coordinates do not hurt, since they 
are corrected by the localization grid in the second step of the coordinate 
transformations.
    However errors on grid indices are problematic during the inverse 
transformation, since they can lead to "no convergence" errors.
---
 .../operation/builder/LinearTransformBuilder.java  | 56 ++++++++++++++++++++--
 .../operation/builder/LocalizationGridBuilder.java |  3 +-
 .../operation/builder/ProjectedTransformTry.java   | 13 +++--
 .../java/org/apache/sis/math/RepeatedVector.java   | 22 ++++++++-
 .../src/main/java/org/apache/sis/math/Vector.java  | 42 ++++++++++++++++
 .../org/apache/sis/math/RepeatedVectorTest.java    | 53 ++++++++++++++++++++
 6 files changed, 181 insertions(+), 8 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
index b36d2ef..ac05e77 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
@@ -37,6 +37,7 @@ import org.opengis.geometry.coordinate.Position;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Line;
@@ -180,6 +181,16 @@ public class LinearTransformBuilder extends 
TransformBuilder {
     private transient double[] correlations;
 
     /**
+     * Whether to compute <cite>target to source</cite> transform instead than 
<cite>source to target</cite>.
+     * This flag can be set before the first invocation of {@link 
#create(MathTransformFactory)}. Note that
+     * if this flag is set, then the {@linkplain #correlations} are values for 
each source dimensions instead
+     * than target dimensions.
+     *
+     * @see #requestReverseCalculation()
+     */
+    private boolean reverseCalculation;
+
+    /**
      * Creates a temporary builder with all source fields from the given 
builder and no target arrays.
      * Calculated fields, namely {@link #correlations} and {@link #transform}, 
are left uninitialized.
      * Arrays are copied by references and their content shall not be 
modified. The new builder should
@@ -1175,6 +1186,34 @@ search:         for (int j=domain(); --j >= 0;) {
     }
 
     /**
+     * Requests {@link #fit()} to compute <cite>target to source</cite> 
transform instead than <cite>source to target</cite>.
+     * This flag can be set before the first invocation of {@link 
#create(MathTransformFactory)}. Note that if this flag is
+     * set, then the {@linkplain #correlation() correlation coefficients} will 
be values for each source dimensions instead
+     * than target dimensions. However the transform returned by {@code 
create(…)} is still the <cite>source to target</cite>
+     * transform; only the linear regression has been computed in the reverse 
way.
+     *
+     * <div class="section">Why reverse calculation</div>
+     * In forward transformation, the linear regression algorithm assumes that 
grid indices are exact and all errors
+     * are in geospatial coordinates. This is a reasonable assumption if the 
linear regression is used directly. But
+     * in {@link LocalizationGridBuilder}, having the smallest errors on 
geospatial coordinates is not so important,
+     * because errors are corrected by the residual grids anyway. However it 
is important than during inverse operation,
+     * the grid indices estimated by the linear regression is as close as 
possible to the real grid indices, otherwise
+     * we get "no convergence" problem. For that reason, {@link 
LocalizationGridBuilder} needs to minimize errors on
+     * the grid indices instead than on the geospatial coordinates.
+     *
+     * <div class="section">Constraints</div>
+     * This method is only for {@link LocalizationGridBuilder} purpose.
+     * Current {@code LinearTransformBuilder} implementation has the following 
restrictions:
+     * <ul>
+     *   <li>Source coordinates must be a two-dimensional grid.</li>
+     *   <li>Source and target dimensions must be equal (in order to have a 
square matrix).</li>
+     * </ul>
+     */
+    final void requestReverseCalculation() {
+        reverseCalculation = (getGridDimensions() == 2) && (targets != null && 
targets.length == 2);
+    }
+
+    /**
      * Creates a linear transform approximation from the source positions to 
the target positions.
      * This method assumes that source positions are precise and that all 
uncertainty is in the target positions.
      * If {@linkplain #addLinearizers linearizers have been specified}, then 
this method may project all target
@@ -1235,7 +1274,7 @@ search:         for (int j=domain(); --j >= 0;) {
                             transformedArrays = tmp.targets;
                             bestCorrelation   = altCorrelation;
                             bestCorrelations  = altCorrelations;
-                            bestTransform     = alt.replace(matrix, 
altTransform);
+                            bestTransform     = alt.replace(matrix, 
altTransform, reverseCalculation);
                             appliedLinearizer = alt;
                         } else {
                             ProjectedTransformTry.recycle(tmp.targets, pool);
@@ -1248,7 +1287,13 @@ search:         for (int j=domain(); --j >= 0;) {
                     correlations = bestCorrelations;
                 }
             }
-            transform = (LinearTransform) 
nonNull(factory).createAffineTransform(matrix);
+            LinearTransform mt = (LinearTransform) 
nonNull(factory).createAffineTransform(matrix);
+            if (reverseCalculation) try {
+                mt = mt.inverse();
+            } catch (NoninvertibleTransformException e) {
+                throw new FactoryException(e);
+            }
+            transform = mt;                                                 // 
Set only on success.
         }
         return transform;
     }
@@ -1303,7 +1348,12 @@ search:         for (int j=domain(); --j >= 0;) {
                     if (sources != null) {
                         c = plan.fit(vector(sources[0]), vector(sources[1]), 
vector(targets[j]));
                     } else try {
-                        c = plan.fit(gridSize[0], gridSize[1], 
Vector.create(targets[j]));
+                        if (reverseCalculation) {
+                            Vector vz = Vector.createSequence(0, 1, 
gridSize[j]).repeat(j != 0, gridSize[j ^ 1]);
+                            c = plan.fit(Vector.create(targets[0]), 
Vector.create(targets[1]), vz);
+                        } else {
+                            c = plan.fit(gridSize[0], gridSize[1], 
Vector.create(targets[j]));
+                        }
                     } catch (IllegalArgumentException e) {
                         // This may happen if the z vector still contain some 
"NaN" values.
                         throw new InvalidGeodeticParameterException(noData(), 
e);
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
index 86a9304..ad9f013 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
@@ -575,7 +575,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
     @Override
     public MathTransform create(final MathTransformFactory factory) throws 
FactoryException {
         if (transform == null) {
-            MathTransform step;
+            linear.requestReverseCalculation();                 // Minimize 
errors in inverse transform.
             final LinearTransform gridToCoord = linear.create(factory);
             /*
              * Make a first check about whether the result of above 
LinearTransformBuilder.create() call
@@ -590,6 +590,7 @@ public class LocalizationGridBuilder extends 
TransformBuilder {
                     break;
                 }
             }
+            MathTransform step;
             if (isExact) {
                 step = MathTransforms.concatenate(sourceToGrid, gridToCoord);
             } else {
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
index abeafcb..50a97e1 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
@@ -284,9 +284,10 @@ final class ProjectedTransformTry implements 
Comparable<ProjectedTransformTry> {
      *
      * @param  transform  the original affine transform. This matrix will not 
be modified.
      * @param  newValues  coefficients computed by {@link 
LinearTransformBuilder} for the dimensions specified at construction time.
+     * @param  reverseCalculation  value of {@link 
LinearTransformBuilder#reverseCalculation}.
      * @return a copy of the given {@code transform} matrix with new 
coefficients overwriting the old values.
      */
-    final MatrixSIS replace(MatrixSIS transform, final MatrixSIS newValues) {
+    final MatrixSIS replace(MatrixSIS transform, final MatrixSIS newValues, 
final boolean reverseCalculation) {
         /*
          * The two matrices shall have the same number of columns because they 
were computed with
          * LinearTransformBuilder instances having the same sources. However 
the two matrices may
@@ -299,8 +300,14 @@ final class ProjectedTransformTry implements 
Comparable<ProjectedTransformTry> {
         transform = transform.clone();
         for (int j=0; j<projToGrid.length; j++) {
             final int d = projToGrid[j];
-            for (int i=transform.getNumRow(); --i >= 0;) {
-                transform.setNumber(d, i, newValues.getNumber(j, i));
+            if (reverseCalculation) {
+                for (int i=transform.getNumRow(); --i >= 0;) {
+                    transform.setNumber(i, d, newValues.getNumber(i, j));
+                }
+            } else {
+                for (int i=transform.getNumCol(); --i >= 0;) {
+                    transform.setNumber(d, i, newValues.getNumber(j, i));
+                }
             }
         }
         return transform;
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java 
b/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
index db76073..b8af882 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
@@ -97,7 +97,7 @@ final class RepeatedVector extends Vector implements 
Serializable {
      * @param cycleLength  length of the sequence of values to repeat.
      * @param size         this vector size, usually {@code base.size() * 
repetition}.
      */
-    private RepeatedVector(final Vector base, final int occurrences, final int 
cycleLength, final int size) {
+    RepeatedVector(final Vector base, final int occurrences, final int 
cycleLength, final int size) {
         this.base        = base;
         this.occurrences = occurrences;
         this.cycleLength = cycleLength;
@@ -189,6 +189,26 @@ final class RepeatedVector extends Vector implements 
Serializable {
     }
 
     /**
+     * Returns a vector whose value is the content of this vector repeated 
<var>count</var> times.
+     */
+    @Override
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    public Vector repeat(final boolean eachValue, final int count) {
+opti:   if (count > 1 && cycleLength * occurrences >= size) {
+            final int n;
+            if (eachValue) {
+                if (cycleLength < base.size()) break opti;
+                n = occurrences;
+            } else {
+                if (occurrences != 1) break opti;
+                n = cycleLength;
+            }
+            return base.repeat(eachValue, Math.multiplyExact(n, count));
+        }
+        return super.repeat(eachValue, count);
+    }
+
+    /**
      * Returns {@code null} since the repetition of a sequence of numbers 
implies that there is no regular increment.
      * An exception to this rule would be if the {@linkplain #base} vector 
contains a constant value or if the repetition
      * is exactly 1, but we should not have created a {@code RepeatedVector} 
in such cases.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java 
b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
index abce606..3c035ea 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
@@ -582,6 +582,8 @@ public abstract class Vector extends AbstractList<Number> 
implements RandomAcces
      *         If no such repetition is found, an empty array.
      *
      * @since 1.0
+     *
+     * @see #repeat(boolean, int)
      */
     public int[] repetitions(int... candidates) {
         if (candidates != null && candidates.length == 0) {
@@ -1232,6 +1234,46 @@ search:     for (;;) {
     }
 
     /**
+     * Returns a vector whose value is the content of this vector repeated 
<var>count</var> times.
+     * The content can be repeated in two different ways:
+     *
+     * <ul>
+     *   <li>If {@code eachValue} is {@code true}, then each value is repeated 
{@code count} times
+     *       before to move to the next value.</li>
+     *   <li>If {@code eachValue} is {@code false}, then whole vector is 
repeated {@code count} times.</li>
+     * </ul>
+     *
+     * <div class="note"><b>Example:</b>
+     * if {@code vec} contains {@code {1, 2, 3}}, then:
+     * <ul>
+     *   <li>{@code vec.repeat(true,  4)} returns {@code {1, 1, 1, 1, 2, 2, 2, 
2, 3, 3, 3, 3}}.</li>
+     *   <li>{@code vec.repeat(false, 4)} returns {@code {1, 2, 3, 1, 2, 3, 1, 
2, 3, 1, 2, 3}}.</li>
+     * </ul></div>
+     *
+     * This method returns an empty vector if {@code count} is zero and 
returns {@code this} if {@code count} is one.
+     * For other positive {@code count} values, this method returns an 
unmodifiable view of this vector:
+     * changes in this vector are reflected in the repeated vector.
+     *
+     * @param  eachValue  whether to apply the repetition on each value 
({@code true}) or on the whole vector ({@code false}).
+     * @param  count      number of repetitions as a positive number 
(including zero).
+     * @return this vector repeated <var>count</var> time.
+     *
+     * @since 1.0
+     *
+     * @see #repetitions(int...)
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    public Vector repeat(final boolean eachValue, final int count) {
+        switch (count) {
+            case 0: return subList(0, 0);
+            case 1: return this;
+        }
+        ArgumentChecks.ensurePositive("count", count);
+        final int size = size();
+        return new RepeatedVector(this, eachValue ? count : 1, size, 
Math.multiplyExact(size, count));
+    }
+
+    /**
      * Returns a view which contains the values of this vector in reverse 
order.
      *
      * <div class="note"><b>Implementation note:</b> this method delegates its 
work
diff --git 
a/core/sis-utility/src/test/java/org/apache/sis/math/RepeatedVectorTest.java 
b/core/sis-utility/src/test/java/org/apache/sis/math/RepeatedVectorTest.java
index b4dceaf..9c8f4da 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/math/RepeatedVectorTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/math/RepeatedVectorTest.java
@@ -169,4 +169,57 @@ public final strictfp class RepeatedVectorTest extends 
TestCase {
         assertArrayEquals(new float[] {10, 10, 10, 12, 15, 18,
                                        10, ip, 10, 12, 15, 18}, 
sub.floatValues(), (float) STRICT);
     }
+
+    /**
+     * Tests {@link Vector#repeat(boolean, int)} with {@code eachValue} set to 
{@code false}.
+     */
+    @Test
+    public void testRepeatVector() {
+        Vector vec = Vector.create(new int[] {2, -4, 7, 3}, false);
+        assertSame(vec, vec.repeat(false, 1));
+
+        vec = vec.repeat(false, 3);
+        assertArrayEquals(new float[] {
+            2, -4, 7, 3,
+            2, -4, 7, 3,
+            2, -4, 7, 3
+        }, vec.floatValues(), (float) STRICT);
+        assertSame(vec, vec.repeat(false, 1));
+
+        vec = vec.repeat(false, 2);
+        assertArrayEquals(new float[] {
+            2, -4, 7, 3,
+            2, -4, 7, 3,
+            2, -4, 7, 3,
+            2, -4, 7, 3,
+            2, -4, 7, 3,
+            2, -4, 7, 3
+        }, vec.floatValues(), (float) STRICT);
+    }
+
+    /**
+     * Tests {@link Vector#repeat(boolean, int)} with {@code eachValue} set to 
{@code true}.
+     */
+    @Test
+    public void testRepeatValues() {
+        Vector vec = Vector.create(new int[] {2, -4, 7, 3}, false);
+        assertSame(vec, vec.repeat(true, 1));
+
+        vec = vec.repeat(true, 3);
+        assertArrayEquals(new float[] {
+             2,  2,  2,
+            -4, -4, -4,
+             7,  7,  7,
+             3,  3,  3
+        }, vec.floatValues(), (float) STRICT);
+        assertSame(vec, vec.repeat(false, 1));
+
+        vec = vec.repeat(true, 2);
+        assertArrayEquals(new float[] {
+             2,  2,  2,  2,  2,  2,
+            -4, -4, -4, -4, -4, -4,
+             7,  7,  7,  7,  7,  7,
+             3,  3,  3,  3,  3,  3
+        }, vec.floatValues(), (float) STRICT);
+    }
 }

Reply via email to