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 27b57b6 Add a LinearTransformBuilder.getControlPoints() method in
complement to setControlPoints(Map). Use that new method for adding a
LocalizationGridBuilder(LinearTransformBuilder) constructor.
27b57b6 is described below
commit 27b57b629a5b1d49eadc6d1d6622433d89f58255
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Oct 5 20:39:06 2018 -0400
Add a LinearTransformBuilder.getControlPoints() method in complement to
setControlPoints(Map).
Use that new method for adding a
LocalizationGridBuilder(LinearTransformBuilder) constructor.
---
.../operation/builder/LinearTransformBuilder.java | 332 ++++++++++++++++++++-
.../operation/builder/LocalizationGridBuilder.java | 54 +++-
.../builder/LinearTransformBuilderTest.java | 81 ++++-
.../builder/LocalizationGridBuilderTest.java | 39 ++-
.../java/org/apache/sis/util/resources/Errors.java | 5 +
.../apache/sis/util/resources/Errors.properties | 1 +
.../apache/sis/util/resources/Errors_fr.properties | 1 +
7 files changed, 494 insertions(+), 19 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 88123bb..e3bb860 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
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.operation.builder;
import java.util.Map;
import java.util.Arrays;
+import java.util.NoSuchElementException;
import java.io.IOException;
import org.opengis.util.FactoryException;
import org.opengis.geometry.Envelope;
@@ -31,12 +32,16 @@ import org.apache.sis.io.TableAppender;
import org.apache.sis.math.Line;
import org.apache.sis.math.Plane;
import org.apache.sis.math.Vector;
+import org.apache.sis.geometry.DirectPosition1D;
+import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.util.AbstractMap;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ArgumentChecks;
@@ -108,7 +113,8 @@ public class LinearTransformBuilder extends
TransformBuilder {
* 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...)}.
+ * has been created by {@link #LinearTransformBuilder(int...)} because we
do not
+ * try to detect if user adds a new point or overwrites an existing one.
*/
private int numPoints;
@@ -177,6 +183,8 @@ public class LinearTransformBuilder extends
TransformBuilder {
/**
* Returns the grid size for the given dimension. It is caller's
responsibility to ensure that
* this method is invoked only on instances created by {@link
#LinearTransformBuilder(int...)}.
+ *
+ * @see #getGridDimensions()
*/
final int gridSize(final int srcDim) {
return gridSize[srcDim];
@@ -214,11 +222,13 @@ public class LinearTransformBuilder extends
TransformBuilder {
/**
* 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.
+ * can not be used. Callers is responsible to ensure that the number of
dimensions match.
+ *
+ * @see ControlPoints#search(double[][], double[])
*/
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++) {
+search: for (int j=numPoints; --j >= 0;) {
for (int i=0; i<source.length; i++) {
if (source[i] != sources[i][j]) {
continue search; // Search
another position for the same source.
@@ -256,6 +266,8 @@ search: for (int j=0; j<numPoints; j++) {
* of known size. Callers must have verified the position dimension before
to invoke this method.
*
* @throws IllegalArgumentException if an ordinate value is illegal.
+ *
+ * @see ControlPoints#flatIndex(DirectPosition)
*/
private int flatIndex(final DirectPosition source) {
assert sources == null; // This method should not be
invoked for randomly distributed points.
@@ -295,7 +307,8 @@ search: for (int j=0; j<numPoints; j++) {
/**
* 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.
+ * that positions are stored in this builder as they are read from
user-provided collection,
+ * with {@link #numPoints} the index of the next point that we failed to
add.
*/
private String mismatchedDimension(final String name, final int expected,
final int actual) {
return Errors.format(Errors.Keys.MismatchedDimension_3, name + '[' +
numPoints + ']', expected, actual);
@@ -309,6 +322,17 @@ search: for (int j=0; j<numPoints; j++) {
}
/**
+ * Returns the number of dimensions in the source grid, or -1 if this
builder is not backed by a grid.
+ * Contrarily to the other {@code get*Dimensions()} methods, this method
does not throw exception.
+ *
+ * @see #getSourceDimensions()
+ * @see #gridSize(int)
+ */
+ final int getGridDimensions() {
+ return (gridSize != null) ? gridSize.length : -1;
+ }
+
+ /**
* Returns the number of dimensions in source positions.
*
* @return the dimension of source points.
@@ -388,11 +412,10 @@ search: for (int j=0; j<numPoints; j++) {
}
final int dim = points.length;
final GeneralEnvelope envelope = new GeneralEnvelope(dim);
- for (int i=0; i <dim; i++) {
- final double[] data = points[i];
+ for (int i=0; i<dim; i++) {
double lower = Double.POSITIVE_INFINITY;
double upper = Double.NEGATIVE_INFINITY;
- for (final double value : data) {
+ for (final double value : points[i]) {
if (value < lower) lower = value;
if (value > upper) upper = value;
}
@@ -416,6 +439,7 @@ search: for (int j=0; j<numPoints; j++) {
* 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. Coordinate reference systems are
ignored.
* Null positions are silently ignored.
+ * Positions with NaN or infinite coordinates cause an exception to be
thrown.
*
* <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>).
@@ -431,6 +455,7 @@ search: for (int j=0; j<numPoints; j++) {
*
* @param sourceToTarget a map of source positions to target positions.
* Source positions are assumed precise and target positions are
assumed uncertain.
+ * @throws IllegalArgumentException if the given positions contain NaN or
infinite coordinate values.
* @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.
@@ -481,19 +506,268 @@ search: for (int j=0; j<numPoints; j++) {
int d;
if ((d = src.getDimension()) != srcDim) throw new
MismatchedDimensionException(mismatchedDimension("source", srcDim, d));
if ((d = tgt.getDimension()) != tgtDim) throw new
MismatchedDimensionException(mismatchedDimension("target", tgtDim, d));
+ boolean isValid = true;
int index;
if (gridSize != null) {
index = flatIndex(src);
} else {
index = numPoints;
for (int i=0; i<srcDim; i++) {
- sources[i][index] = src.getOrdinate(i);
+ isValid &= Double.isFinite(sources[i][index] =
src.getOrdinate(i));
}
}
for (int i=0; i<tgtDim; i++) {
- targets[i][index] = tgt.getOrdinate(i);
+ isValid &= Double.isFinite(targets[i][index] =
tgt.getOrdinate(i));
+ }
+ /*
+ * If the point contains some NaN or infinite coordinate values,
it is okay to leave it as-is
+ * (without incrementing 'numPoints') provided that we ensure that
at least one value is NaN.
+ * For convenience, we set only the first coordinate to NaN. The
ControlPoints map will check
+ * for the first coordinate too, so we need to keep this policy
consistent.
+ */
+ if (isValid) {
+ numPoints++;
+ } else {
+ targets[0][index] = Double.NaN;
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalMapping_2, src, tgt));
}
- numPoints++;
+ }
+ }
+
+ /**
+ * Returns all control points as a map. Values are source coordinates and
keys are target coordinates.
+ * The map is unmodifiable and is guaranteed to contain only non-null keys
and values.
+ * The map is a view: changes in this builder are immediately reflected in
the returned map.
+ *
+ * @return all control points in this builder.
+ *
+ * @since 1.0
+ */
+ public Map<DirectPosition,DirectPosition> getControlPoints() {
+ return (gridSize != null) ? new ControlPoints() : new Ungridded();
+ }
+
+ /**
+ * Implementation of the map returned by {@link #getControlPoints()}. The
default implementation
+ * is suitable for {@link LinearTransformBuilder} backed by a grid. For
non-gridded sources, the
+ * {@link Ungridded} subclass shall be used instead.
+ */
+ private class ControlPoints extends
AbstractMap<DirectPosition,DirectPosition> {
+ /**
+ * Creates a new map view of control points.
+ */
+ ControlPoints() {
+ }
+
+ /**
+ * Creates a point from the given data at the given offset. Before to
invoke this method,
+ * caller should verify index validity and that the coordinate does
not contain NaN values.
+ */
+ final DirectPosition position(final double[][] data, final int offset)
{
+ switch (data.length) {
+ case 1: return new DirectPosition1D(data[0][offset]);
+ case 2: return new DirectPosition2D(data[0][offset],
data[1][offset]);
+ }
+ final GeneralDirectPosition pos = new
GeneralDirectPosition(data.length);
+ for (int i=0; i<data.length; i++) pos.setOrdinate(i,
data[i][offset]);
+ return pos;
+ }
+
+ /**
+ * Returns the number of points to consider when searching in {@link
#sources} or {@link #targets} arrays.
+ * For gridded data we can not rely on {@link #numPoints} because the
coordinate values may be at any index,
+ * not necessarily at consecutive indices.
+ */
+ int domain() {
+ return gridLength;
+ }
+
+ /**
+ * Returns the index of the given coordinates in the given data array
(source or target coordinates).
+ * This method is a copy of {@link
LinearTransformBuilder#search(int[])}, but working on real values
+ * instead than integers and capable to work on {@link #targets} as
well as {@link #sources}.
+ *
+ * <p>If the given coordinates contain NaN values, then this method
will always return -1 even if the
+ * given data contains the same NaN values. We want this behavior
because NaN mean that the point has
+ * not been set. There is no confusion with NaN values that users
could have set explicitly because
+ * {@code setControlPoint} methods do not allow NaN values.</p>
+ *
+ * @see LinearTransformBuilder#search(int[])
+ */
+ final int search(final double[][] data, final double[] coord) {
+ if (data != null && coord.length == data.length) {
+search: for (int j=domain(); --j >= 0;) {
+ for (int i=0; i<coord.length; i++) {
+ if (coord[i] != data[i][j]) { //
Intentionally want 'false' for NaN values.
+ continue search;
+ }
+ }
+ return j;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns {@code true} if the given value is one of the target
coordinates.
+ * This method requires a linear scan of the data.
+ */
+ @Override
+ public final boolean containsValue(final Object value) {
+ return (value instanceof Position) && search(targets, ((Position)
value).getDirectPosition().getCoordinate()) >= 0;
+ }
+
+ /**
+ * Returns {@code true} if the given value is one of the source
coordinates.
+ * This method is fast on gridded data, but requires linear scan on
non-gridded data.
+ */
+ @Override
+ public final boolean containsKey(final Object key) {
+ return (key instanceof Position) && flatIndex(((Position)
key).getDirectPosition()) >= 0;
+ }
+
+ /**
+ * Returns the target point for the given source point.
+ * This method is fast on gridded data, but requires linear scan on
non-gridded data.
+ */
+ @Override
+ public final DirectPosition get(final Object key) {
+ if (key instanceof Position) {
+ final int index = flatIndex(((Position)
key).getDirectPosition());
+ if (index >= 0) return position(targets, index);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the index where to fetch a target position for the given
source position in the flattened array.
+ * This is the same work as {@link
LinearTransformBuilder#flatIndex(DirectPosition)}, but without throwing
+ * exception if the position is invalid. Instead, -1 is returned as a
sentinel value for invalid source
+ * (including mismatched number of dimensions).
+ *
+ * <p>The default implementation assumes a grid. This method must be
overridden by {@link Ungridded}.</p>
+ *
+ * @see LinearTransformBuilder#flatIndex(DirectPosition)
+ */
+ int flatIndex(final DirectPosition source) {
+ final double[][] targets = LinearTransformBuilder.this.targets;
+ if (targets != null) {
+ final int[] gridSize = LinearTransformBuilder.this.gridSize;
+ int i = gridSize.length;
+ if (i == source.getDimension()) {
+ int offset = 0;
+ while (i != 0) {
+ final int size = gridSize[--i];
+ final double ordinate = source.getOrdinate(i);
+ final int index = (int) ordinate;
+ if (index < 0 || index >= size || index != ordinate) {
+ return -1;
+ }
+ offset = offset * size + index;
+ }
+ if (!Double.isNaN(targets[0][offset])) return offset;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns an iterator over the entries.
+ * {@code DirectPosition} instances are created on-the-fly during the
iteration.
+ *
+ * <p>The default implementation assumes a grid. This method must be
overridden by {@link Ungridded}.</p>
+ */
+ @Override
+ protected EntryIterator<DirectPosition,DirectPosition> entryIterator()
{
+ return new EntryIterator<DirectPosition,DirectPosition>() {
+ /**
+ * Index in the flat arrays of the next entry to return.
+ */
+ private int index = -1;
+
+ /**
+ * Moves to the next entry and returns {@code true} if an
entry has been found.
+ * This method skips coordinates having NaN value. Those NaN
values may happen
+ * on gridded data (they mean that the point has not yet been
set), but should
+ * not happen on non-gridded data.
+ */
+ @Override protected boolean next() {
+ final double[][] targets =
LinearTransformBuilder.this.targets;
+ if (targets != null) {
+ final double[] x = targets[0];
+ final int gridLength =
LinearTransformBuilder.this.gridLength;
+ while (++index < gridLength) {
+ if (!Double.isNaN(x[index])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Reconstructs the source coordinates for the current index.
+ * This method is the converse of {@code
ControlPoints.flatIndex(DirectPosition)}.
+ * It assumes gridded data; {@link Ungridded} will have to do
a different work.
+ */
+ @Override protected DirectPosition getKey() {
+ final int[] gridSize =
LinearTransformBuilder.this.gridSize;
+ final int dim = gridSize.length;
+ final GeneralDirectPosition pos = new
GeneralDirectPosition(dim);
+ int offset = index;
+ for (int i=0; i<dim; i++) {
+ final int size = gridSize[i];
+ pos.setOrdinate(i, offset % size);
+ offset /= size;
+ }
+ if (offset == 0) {
+ return pos;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ /**
+ * Returns the target coordinates at current index.
+ */
+ @Override protected DirectPosition getValue() {
+ return position(targets, index);
+ }
+ };
+ }
+ }
+
+ /**
+ * Implementation of the map returned by {@link #getControlPoints()} when
no grid is used.
+ * This implementation is simpler than the gridded case, but less
efficient as some methods
+ * require a linear scan.
+ */
+ private final class Ungridded extends ControlPoints {
+ /** Overrides default method with more efficient implementation. */
+ @Override public boolean isEmpty() {return numPoints == 0;}
+ @Override public int size() {return numPoints;}
+ @Override int domain() {return numPoints;}
+
+ /**
+ * Returns the index where to fetch a target position for the given
source position
+ * in the flattened array. In non-gridded case, this operation
requires linear scan.
+ */
+ @Override int flatIndex(final DirectPosition source) {
+ return search(sources, source.getCoordinate());
+ }
+
+ /**
+ * Returns an iterator over the entries.
+ * {@code DirectPosition} instances are created on-the-fly during the
iteration.
+ */
+ @Override protected EntryIterator<DirectPosition,DirectPosition>
entryIterator() {
+ return new EntryIterator<DirectPosition,DirectPosition>() {
+ private int index = -1;
+
+ @Override protected boolean next() {return ++index
< numPoints;}
+ @Override protected DirectPosition getKey() {return
position(sources, index);}
+ @Override protected DirectPosition getValue() {return
position(targets, index);}
+ };
}
}
@@ -511,7 +785,7 @@ search: for (int j=0; j<numPoints; j++) {
* 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.
+ * but some source ordinates are out of index range, or if {@code
target} contains NaN of infinite numbers.
* @throws MismatchedDimensionException if the source or target position
does not have the expected number of dimensions.
*
* @since 0.8
@@ -554,8 +828,15 @@ search: for (int j=0; j<numPoints; j++) {
sources[i][index] = source[i];
}
}
+ boolean isValid = true;
for (int i=0; i<tgtDim; i++) {
- targets[i][index] = target[i];
+ isValid &= Double.isFinite(targets[i][index] = target[i]);
+ }
+ transform = null;
+ correlation = null;
+ if (!isValid) {
+ if (gridSize == null) numPoints--;
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalMapping_2, source,
target));
}
}
@@ -593,12 +874,18 @@ search: for (int j=0; j<numPoints; j++) {
return null;
}
}
- boolean isNaN = true;
+ /*
+ * A coordinate with NaN value means that the point has not been set.
+ * Not that the coordinate may have only one NaN value, not necessarily
+ * all of them, if the point has been deleted after insertion attempt.
+ */
final double[] target = new double[targets.length];
for (int i=0; i<target.length; i++) {
- isNaN &= Double.isNaN(target[i] = targets[i][index]);
+ if (Double.isNaN(target[i] = targets[i][index])) {
+ return null;
+ }
}
- return isNaN ? null : target;
+ return target;
}
/**
@@ -616,6 +903,21 @@ search: for (int j=0; j<numPoints; j++) {
}
/**
+ * Returns the vector of source ordinate names.
+ * It is caller responsibility to ensure that this builder is not backed
by a grid.
+ */
+ final Vector[] sources() {
+ if (sources != null) {
+ final Vector[] v = new Vector[sources.length];
+ for (int i=0; i<v.length; i++) {
+ v[i] = vector(sources[i]);
+ }
+ return v;
+ }
+ throw new IllegalStateException(noData());
+ }
+
+ /**
* 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.
*
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 f0abca3..7fc6e80 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
@@ -134,7 +134,9 @@ public class LocalizationGridBuilder extends
TransformBuilder {
*/
public LocalizationGridBuilder(final Vector sourceX, final Vector sourceY)
{
final Matrix fromGrid = new Matrix3();
- linear = new LinearTransformBuilder(infer(sourceX, fromGrid, 0),
infer(sourceY, fromGrid, 1));
+ final int width = infer(sourceX, fromGrid, 0);
+ final int height = infer(sourceY, fromGrid, 1);
+ linear = new LinearTransformBuilder(width, height);
try {
sourceToGrid = MathTransforms.linear(fromGrid).inverse();
} catch (NoninvertibleTransformException e) {
@@ -144,6 +146,56 @@ public class LocalizationGridBuilder extends
TransformBuilder {
}
/**
+ * Creates a new builder for a localization grid inferred from the given
provider of control points.
+ * The {@linkplain LinearTransformBuilder#getSourceDimensions() number of
source dimensions} in the
+ * given {@code localizations} argument shall be 2. The {@code
localization} can be used in two ways:
+ *
+ * <ul class="verbose">
+ * <li>If the {@code localizations} instance has been
+ * {@linkplain LinearTransformBuilder#LinearTransformBuilder(int...)
created with a fixed grid size},
+ * then that instance is used as-is — it is not copied. It is okay to
specify an empty instance and
+ * to provide control points later by calls to {@link
#setControlPoint(int, int, double...)}.</li>
+ * <li>If the {@code localizations} instance has been
+ * {@linkplain LinearTransformBuilder#LinearTransformBuilder() created
for a grid of unknown size},
+ * then this constructor tries to infer a grid size by inspection of
the control points present in
+ * {@code localizations} at the time this constructor is invoked.
Changes in {@code localizations}
+ * after construction will not be reflected in this new builder.</li>
+ * </ul>
+ *
+ * @param localizations the provider of control points for which to
create a localization grid.
+ * @throws ArithmeticException if this constructor can not infer a
reasonable grid size from the given localizations.
+ *
+ * @since 1.0
+ */
+ public LocalizationGridBuilder(final LinearTransformBuilder localizations)
{
+ ArgumentChecks.ensureNonNull("localizations", localizations);
+ int n = localizations.getGridDimensions();
+ if (n == 2) {
+ linear = localizations;
+ sourceToGrid = MathTransforms.identity(2);
+ } else {
+ if (n < 0) {
+ final Vector[] sources = localizations.sources();
+ n = sources.length;
+ if (n == 2) {
+ final Matrix fromGrid = new Matrix3();
+ final int width = infer(sources[0], fromGrid, 0);
+ final int height = infer(sources[1], fromGrid, 1);
+ linear = new LinearTransformBuilder(width, height);
+ linear.setControlPoints(localizations.getControlPoints());
+ try {
+ sourceToGrid =
MathTransforms.linear(fromGrid).inverse();
+ } catch (NoninvertibleTransformException e) {
+ throw (ArithmeticException) new
ArithmeticException(e.getLocalizedMessage()).initCause(e);
+ }
+ return;
+ }
+ }
+ throw new
IllegalArgumentException(Resources.format(Resources.Keys.MismatchedTransformDimension_3,
0, 2, n));
+ }
+ }
+
+ /**
* Infers a grid size by searching for the greatest common divisor (GCD)
for values in the given vector.
* The vector values should be integers, but this method is tolerant to
constant offsets (typically 0.5).
* The GCD is taken as a "grid to source" scale factor and the minimal
value as the translation term.
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
index 9c79d50..a36608e 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
@@ -29,14 +29,15 @@ import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.TestCase;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
+import org.opengis.geometry.DirectPosition;
/**
* Tests {@link LinearTransformBuilder}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.5
* @module
*/
@@ -329,4 +330,80 @@ public final strictfp class LinearTransformBuilderTest
extends TestCase {
assertEquals("m₁₂", ref.getTranslateY(), m.getElement(1, 2),
translationTolerance);
assertArrayEquals("correlation", new double[] {1, 1},
builder.correlation(), scaleTolerance);
}
+
+ /**
+ * Tests {@link LinearTransformBuilder#getControlPoints()} with gridded
source points.
+ */
+ @Test
+ public void testGetControlPoints() {
+ testGetControlPoints(new LinearTransformBuilder(3, 4));
+ }
+
+ /**
+ * Tests {@link LinearTransformBuilder#getControlPoints()} with
non-gridded source points.
+ */
+ @Test
+ public void testGetUngriddedControlPoints() {
+ testGetControlPoints(new LinearTransformBuilder());
+ }
+
+ /**
+ * Tests {@link LinearTransformBuilder#getControlPoints()} with the given
builder.
+ * If the builder is backed by a grid, then the grid size shall be at
least 3×4.
+ */
+ private static void testGetControlPoints(final LinearTransformBuilder
builder) {
+ final DirectPosition2D s12, s23, s00;
+ final DirectPosition2D t12, t23, t00;
+ s12 = new DirectPosition2D(1, 2); t12 = new DirectPosition2D(3, 2);
+ s23 = new DirectPosition2D(2, 3); t23 = new DirectPosition2D(4, 1);
+ s00 = new DirectPosition2D(0, 0); t00 = new DirectPosition2D(7, 3);
+
+ final Map<DirectPosition2D,DirectPosition2D> expected = new
HashMap<>();
+ final Map<DirectPosition,DirectPosition> actual =
builder.getControlPoints();
+ assertEquals(0, actual.size());
+ assertTrue(actual.isEmpty());
+ assertFalse(actual.containsKey (s12));
+ assertFalse(actual.containsKey (s23));
+ assertFalse(actual.containsKey (s00));
+ assertFalse(actual.containsValue(t12));
+ assertFalse(actual.containsValue(t23));
+ assertFalse(actual.containsValue(t00));
+ assertMapEquals(expected, actual);
+
+ builder.setControlPoint(new int[] {1, 2}, t12.getCoordinate());
+ assertNull(expected.put(s12, t12));
+ assertEquals(1, actual.size());
+ assertFalse(actual.isEmpty());
+ assertTrue (actual.containsKey (s12));
+ assertFalse(actual.containsKey (s23));
+ assertFalse(actual.containsKey (s00));
+ assertTrue (actual.containsValue(t12));
+ assertFalse(actual.containsValue(t23));
+ assertFalse(actual.containsValue(t00));
+ assertMapEquals(expected, actual);
+
+ builder.setControlPoint(new int[] {2, 3}, t23.getCoordinate());
+ assertNull(expected.put(s23, t23));
+ assertEquals(2, actual.size());
+ assertFalse(actual.isEmpty());
+ assertTrue (actual.containsKey (s12));
+ assertTrue (actual.containsKey (s23));
+ assertFalse(actual.containsKey (s00));
+ assertTrue (actual.containsValue(t12));
+ assertTrue (actual.containsValue(t23));
+ assertFalse(actual.containsValue(t00));
+ assertMapEquals(expected, actual);
+
+ builder.setControlPoint(new int[] {0, 0}, t00.getCoordinate());
+ assertNull(expected.put(s00, t00));
+ assertEquals(3, actual.size());
+ assertFalse(actual.isEmpty());
+ assertTrue (actual.containsKey (s12));
+ assertTrue (actual.containsKey (s23));
+ assertTrue (actual.containsKey (s00));
+ assertTrue (actual.containsValue(t12));
+ assertTrue (actual.containsValue(t23));
+ assertTrue (actual.containsValue(t00));
+ assertMapEquals(expected, actual);
+ }
}
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilderTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilderTest.java
index ecba94d..11a3201 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilderTest.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilderTest.java
@@ -21,21 +21,29 @@ import java.awt.geom.AffineTransform;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.test.referencing.TransformTestCase;
+import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.test.DependsOn;
import org.junit.Test;
+import static org.apache.sis.test.ReferencingAssert.*;
+
/**
* Tests {@link LocalizationGridBuilder}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.8
* @module
*/
@DependsOn({LinearTransformBuilderTest.class, ResidualGridTest.class})
public final strictfp class LocalizationGridBuilderTest extends
TransformTestCase {
/**
+ * For floating-point comparisons.
+ */
+ private static final double STRICT = 0;
+
+ /**
* Creates a builder initialized with control points computed from the
given affine transform.
* Some non-linear terms will be added to the coordinates computed by the
given transform.
*
@@ -102,4 +110,33 @@ public final strictfp class LocalizationGridBuilderTest
extends TransformTestCas
verifyTransform(new double[] {0, 3}, new double[] { 1.3, -8.5});
verifyTransform(new double[] {4, 3}, new double[] { 87.7, -123.7});
}
+
+ /**
+ * Tests {@link
LocalizationGridBuilder#LocalizationGridBuilder(LinearTransformBuilder)}.
+ *
+ * @throws TransformException if an error occurred while computing the
envelope.
+ */
+ @Test
+ public void testCreateFromLocalizations() throws TransformException {
+ final LinearTransformBuilder localizations = new
LinearTransformBuilder();
+ localizations.setControlPoint(new int[] {0, 0}, new double[] {-20.0,
8.0});
+ localizations.setControlPoint(new int[] {1, 0}, new double[] { 0.4,
-21.7});
+ localizations.setControlPoint(new int[] {0, 1}, new double[] {-14.3,
3.5});
+ localizations.setControlPoint(new int[] {1, 1}, new double[] { 6.1,
-26.2});
+ localizations.setControlPoint(new int[] {0, 2}, new double[] { 1.3,
-8.5});
+ localizations.setControlPoint(new int[] {1, 2}, new double[] { 87.7,
-123.7});
+ LocalizationGridBuilder builder = new
LocalizationGridBuilder(localizations);
+ /*
+ * Verifies the grid size by checking the source envelope.
+ * Minimum and maximum values are inclusive.
+ */
+ assertEnvelopeEquals(new Envelope2D(null, 0, 0, 1, 2),
builder.getSourceEnvelope(false), STRICT);
+ /*
+ * Verify a few random positions.
+ */
+ assertArrayEquals(new double[] {-20.0, 8.0},
builder.getControlPoint(0, 0), STRICT);
+ assertArrayEquals(new double[] { 0.4, -21.7},
builder.getControlPoint(1, 0), STRICT);
+ assertArrayEquals(new double[] { 1.3, -8.5},
builder.getControlPoint(0, 2), STRICT);
+ assertArrayEquals(new double[] { 87.7, -123.7},
builder.getControlPoint(1, 2), STRICT);
+ }
}
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index 365cdee..67668b2 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -383,6 +383,11 @@ public final class Errors extends IndexedResourceBundle {
public static final short IllegalLanguageCode_1 = 54;
/**
+ * Illegal mapping: {0} → {1}.
+ */
+ public static final short IllegalMapping_2 = 185;
+
+ /**
* Member “{0}” can not be associated to type “{1}”.
*/
public static final short IllegalMemberType_2 = 55;
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
index d238722..926be8e 100644
---
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
+++
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
@@ -87,6 +87,7 @@ IllegalCRSType_1 = Coordinate reference
system can not be of ty
IllegalFormatPatternForClass_2 = The \u201c{1}\u201d pattern can not be
applied to formatting of objects of type \u2018{0}\u2019.
IllegalIdentifierForCodespace_2 = \u201c{1}\u201d is not a valid identifier
for the \u201c{0}\u201d code space.
IllegalLanguageCode_1 = The \u201c{0}\u201d language is not
recognized.
+IllegalMapping_2 = Illegal mapping: {0} \u2192 {1}.
IllegalMemberType_2 = Member \u201c{0}\u201d can not be
associated to type \u201c{1}\u201d.
IllegalOptionValue_2 = Option \u2018{0}\u2019 can not take the
\u201c{1}\u201d value.
IllegalOrdinateRange_3 = The [{0} \u2026 {1}] range of ordinate
values is not valid for the \u201c{2}\u201d axis.
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
index 176b593..4beb847 100644
---
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
+++
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
@@ -84,6 +84,7 @@ IllegalCRSType_1 = Le syst\u00e8me de
r\u00e9f\u00e9rence des c
IllegalFormatPatternForClass_2 = Le mod\u00e8le \u00ab\u202f{1}\u202f\u00bb
ne peut pas \u00eatre appliqu\u00e9 au formatage d\u2019objets de type
\u2018{0}\u2019.
IllegalIdentifierForCodespace_2 = \u00ab\u202f{1}\u202f\u00bb n\u2019est pas
un identifiant valide pour l\u2019espace de codes \u00ab\u202f{0}\u202f\u00bb.
IllegalLanguageCode_1 = Le code de langue
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas reconnu.
+IllegalMapping_2 = Correspondance ill\u00e9gale: {0} \u2192
{1}.
IllegalMemberType_2 = Le membre \u00ab\u202f{0}\u202f\u00bb ne
peut pas \u00eatre associ\u00e9 au type \u00ab\u202f{1}\u202f\u00bb.
IllegalOptionValue_2 = L\u2019option \u2018{0}\u2019
n\u2019accepte pas la valeur \u00ab\u202f{1}\u202f\u00bb.
IllegalOrdinateRange_3 = La plage de valeurs de coordonn\u00e9es
[{0} \u2026 {1}] n\u2019est pas valide pour l\u2019axe
\u00ab\u202f{2}\u202f\u00bb.