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
commit ef9e4355d6837e0a20f29a8734fe81db2db48fe5 Author: Martin Desruisseaux <[email protected]> AuthorDate: Sun Mar 21 21:35:15 2021 +0100 Add `LinearTransformBuilder.approximate(MathTransform, int...)` for building linear approximation of a non-linear transform in a given domain. --- .../apache/sis/internal/referencing/Resources.java | 5 ++ .../sis/internal/referencing/Resources.properties | 1 + .../internal/referencing/Resources_fr.properties | 1 + .../sis/referencing/datum/DatumShiftGrid.java | 3 + .../operation/builder/LinearTransformBuilder.java | 91 ++++++++++++++++++++++ .../operation/builder/ResidualGrid.java | 71 +++++++++++++++-- .../operation/transform/DatumShiftTransform.java | 2 + .../operation/transform/InterpolatedTransform.java | 13 +++- .../builder/LinearTransformBuilderTest.java | 37 +++++++++ .../builder/LocalizationGridBuilderTest.java | 38 ++++++++- .../main/java/org/apache/sis/util/ArraysExt.java | 18 +++++ 11 files changed, 272 insertions(+), 8 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java index 1d1710e..5992138 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java @@ -496,6 +496,11 @@ public final class Resources extends IndexedResourceBundle { public static final short ParameterNotFound_2 = 61; /** + * Points are not on a regular grid. + */ + public static final short PointsAreNotOnRegularGrid = 99; + + /** * Recursive call while creating an object of type ‘{0}’ for code “{1}”. */ public static final short RecursiveCreateCallForCode_2 = 62; diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties index 7e356ce..0f2a5dd 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties @@ -111,6 +111,7 @@ NoSuchAuthorityCode_3 = No \u2018{1}\u2019 object found for code \u2 NoSuchAuthorityCodeInSubset_4 = No \u2018{1}\u2019 object found for code \u201c{2}\u201d. However only a subset of the {0} geodetic dataset has been queried. See {3} for instruction about how to install the full {0} database. NoSuchOperationMethod_1 = No operation method found for name or identifier \u201c{0}\u201d. ParameterNotFound_2 = No parameter named \u201c{1}\u201d has been found in \u201c{0}\u201d. +PointsAreNotOnRegularGrid = Points are not on a regular grid. RecursiveCreateCallForCode_2 = Recursive call while creating an object of type \u2018{0}\u2019 for code \u201c{1}\u201d. SingularMatrix = Matrix is singular. StartOrEndPointNotSet_1 = The {0,choice,0#start|1#end} point has not been specified. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties index e789f0e..17d1c77 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties @@ -116,6 +116,7 @@ NoSuchAuthorityCode_3 = Aucun objet de type \u2018{1}\u2019 n\u2019a NoSuchAuthorityCodeInSubset_4 = Aucun objet de type \u2018{1}\u2019 n\u2019a \u00e9t\u00e9 trouv\u00e9 pour le code \u00ab\u202f{2}\u202f\u00bb. Toutefois seul un sous-ensemble de la base de donn\u00e9es g\u00e9od\u00e9siques {0} a \u00e9t\u00e9 interrog\u00e9. Voir {3} pour des instructions d\u2019installation de la base de donn\u00e9es {0} compl\u00e8te. NoSuchOperationMethod_1 = Aucune m\u00e9thode n\u2019a \u00e9t\u00e9 trouv\u00e9e pour le nom ou l\u2019identifiant \u00ab\u202f{0}\u202f\u00bb. ParameterNotFound_2 = Aucun param\u00e8tre nomm\u00e9 \u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9 dans \u00ab\u202f{0}\u202f\u00bb. +PointsAreNotOnRegularGrid = Les points ne sont pas sur une grille r\u00e9guli\u00e8re. RecursiveCreateCallForCode_2 = Appels r\u00e9cursifs lors de la cr\u00e9ation d\u2019un objet de type \u2018{0}\u2019 pour le code \u00ab\u202f{1}\u202f\u00bb. SingularMatrix = La matrice est singuli\u00e8re. StartOrEndPointNotSet_1 = Le point {0,choice,0#de d\u00e9part|1#d\u2019arriv\u00e9} n\u2019a pas \u00e9t\u00e9 d\u00e9fini. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java index ab08860..08cc73b 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java @@ -213,6 +213,9 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T /** * Number of grid cells along each dimension. This is usually an array of length {@value #INTERPOLATED_DIMENSIONS} * containing the number of grid cells along the <var>x</var> and <var>y</var> axes. + * + * @see #getGridSize() + * @see #getGridSize(int) */ private final int[] gridSize; 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 46377c4..c70b38a 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 @@ -36,6 +36,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.TransformException; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.io.TableAppender; import org.apache.sis.math.Line; @@ -248,6 +249,43 @@ public class LinearTransformBuilder extends TransformBuilder { } /** + * Returns a linear approximation of the given transform for the specified domain. + * The source positions are all integer coordinates in a rectangle from (0, 0, …) inclusive + * to {@code gridSize} exclusive. The target positions are the results of transforming all + * source coordinates with the given {@code gridToCRS} transform. + * This method is equivalent to the following code, but potentially more efficient: + * + * {@preformat java + * val builder = new LinearTransformBuilder(gridSize); + * builder.setControlPoints(gridToCRS); + * return builder.create(null); + * } + * + * @param gridToCRS the transform from source coordinates (grid indices) to target coordinates. + * @param gridSize the upper values (exclusive) of source coordinates. + * @return a linear approximation of given transform for the specified domain. + * @throws FactoryException if the transform approximation can not be computed. + * + * @see #setControlPoints(MathTransform) + * + * @since 1.1 + */ + public static LinearTransform approximate(final MathTransform gridToCRS, final int... gridSize) throws FactoryException { + ArgumentChecks.ensureNonNull("gridToCRS", gridToCRS); + try { + final LinearTransform mt = ResidualGrid.approximate(gridToCRS, gridSize); + if (mt != null) { + return mt; + } + final LinearTransformBuilder builder = new LinearTransformBuilder(gridSize); + builder.setControlPoints(gridToCRS); + return builder.create(null); + } catch (TransformException e) { + throw new FactoryException(e); + } + } + + /** * 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...)}. * @@ -533,6 +571,59 @@ search: for (int j=numPoints; --j >= 0;) { /** * Sets all control point (source, target) pairs, overwriting any previous setting. + * The source positions are all integer coordinates in a rectangle from (0, 0, …) inclusive + * to {@code gridSize} exclusive where {@code gridSize} is an {@code int[]} array specified + * {@linkplain #LinearTransformBuilder(int...) at construction time}. The target positions are + * the results of transforming all source coordinates with the given {@code gridToCRS} transform. + * + * @param gridToCRS the transform from source coordinates (grid indices) to target coordinates. + * @throws TransformException if a coordinate value can not be transformed. + * + * @see #approximate(MathTransform, int...) + * + * @since 1.1 + */ + public void setControlPoints(final MathTransform gridToCRS) throws TransformException { + ArgumentChecks.ensureNonNull("gridToCRS", gridToCRS); + if (gridSize == null) { + throw new IllegalStateException(Resources.format(Resources.Keys.PointsAreNotOnRegularGrid)); + } + final int srcDim = gridToCRS.getSourceDimensions(); + if (srcDim != gridSize.length) { + throw new MismatchedDimensionException(Errors.format( + Errors.Keys.MismatchedTransformDimension_4, "gridToCRS", 0, gridSize.length, srcDim)); + } + final int tgtDim = gridToCRS.getTargetDimensions(); + allocate(tgtDim); + final int width = gridSize[0]; // We will transform points by chunks of 1 line. + final int widthTarget = width * tgtDim; + final double[] buffer = new double[Math.max(srcDim, tgtDim) * width]; + final double[] point = new double[srcDim]; + for (int j=0; j<gridLength; j += width) { + // Prepare one row of coordinates with only x varying: (0,y,z), (1,y,z), (2,y,z), (3,y,z), etc. + for (int i=0; i<width; i++) { + point[0] = i; + System.arraycopy(point, 0, buffer, i*srcDim, srcDim); + } + // Transform and store the result in the target arrays. + gridToCRS.transform(buffer, 0, buffer, 0, width); + for (int dim=0; dim<tgtDim; dim++) { + final double[] target = targets[dim]; + int i = j; + for (int t=dim; t < widthTarget; t += tgtDim) { + target[i++] = buffer[t]; + } + } + // Increment y coordinaate, then z (if any) if needed. + for (int i=1; i<gridSize.length; i++) { + if (point[i]++ < gridSize[i]) break; + point[i] = 0; + } + } + } + + /** + * Sets all control point (source, target) pairs, overwriting any previous setting. * The source positions are the keys in given map, and the target positions are the associated values. * The map should not contain two entries with the same source position. * Coordinate reference systems are ignored. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java index a3078f7..bce70ef 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java @@ -22,14 +22,19 @@ import javax.measure.quantity.Dimensionless; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.apache.sis.parameter.Parameters; import org.apache.sis.parameter.ParameterBuilder; import org.apache.sis.referencing.datum.DatumShiftGrid; import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.operation.transform.LinearTransform; import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.apache.sis.referencing.operation.transform.InterpolatedTransform; import org.apache.sis.internal.referencing.WKTUtilities; +import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.geometry.Envelopes; import org.apache.sis.internal.util.Constants; import org.apache.sis.internal.util.Numerics; import org.apache.sis.io.wkt.FormattableObject; @@ -37,6 +42,7 @@ import org.apache.sis.io.wkt.Formatter; import org.apache.sis.math.Statistics; import org.apache.sis.math.Vector; import org.apache.sis.measure.Units; +import org.apache.sis.util.ArraysExt; /** @@ -96,9 +102,8 @@ final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> { MatrixSIS m = ((ContextualParameters) parameters).getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); m.setMatrix(denormalization); } - final int[] size = getGridSize(); - parameters.parameter(Constants.NUM_ROW).setValue(size[1]); - parameters.parameter(Constants.NUM_COL).setValue(size[0]); + parameters.parameter(Constants.NUM_ROW).setValue(getGridSize(1)); + parameters.parameter(Constants.NUM_COL).setValue(getGridSize(0)); parameters.parameter("grid_x").setValue(new Data(0, denormalization)); parameters.parameter("grid_y").setValue(new Data(1, denormalization)); } @@ -177,7 +182,6 @@ final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> { this.offsets = residuals; this.accuracy = precision; this.scanlineStride = nx; - double[] periodVector = null; if (periods != null && linearizer == null && gridToTarget.isAffine()) { /* * We require the transform to be affine because it makes the Jacobian independent of @@ -199,8 +203,9 @@ final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> { * period of 12 months, then `replaceOutsideGridCoordinates(…)` will only shift by 360° * AND 12 months together, never 360° only or 12 months only. */ + } else { + periodVector = null; } - this.periodVector = periodVector; } /** @@ -300,7 +305,7 @@ final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> { * Geocentric interpolations add the translation to coordinates converted to geocentric coordinates.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.1 * @since 1.0 * @module */ @@ -325,6 +330,9 @@ final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> { /** Computes the matrix element in the given row and column. */ @Override public double getElement(final int y, final int x) { + if ((x | y) < 0 || x >= scanlineStride) { + throw new IndexOutOfBoundsException(); + } return c0 * (x + getCellValue(0, x, y)) + // TODO: use Math.fma with JDK9. c1 * (y + getCellValue(1, x, y)) + c2; @@ -387,6 +395,57 @@ final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> { } /** + * If a linear approximation can be extracted from the given transform, returns that approximation. + * The approximation applies to source coordinates in the range 0 to {@code size[i]} for each source + * dimension <var>i</var>. + * + * <p>This method does not perform expansive calculation; it searches only for transforms than can + * be processed easily. This method is defined in this class because it checks for transformations + * backed by {@link ResidualGrid} instances.</p> + * + * @param size upper coordinate values of the source domain for which to get a linear approximation. + * @return the linear approximation of given transform, or {@code null} if none readily available. + */ + static LinearTransform approximate(final MathTransform gridToCRS, final int[] size) throws TransformException { + if (size.length != SOURCE_DIMENSION) { + return null; + } + MathTransform result = null; + for (final MathTransform step : MathTransforms.getSteps(gridToCRS)) { + if (step instanceof LinearTransform) { + if (result == null) { + result = step; + } else { + result = MathTransforms.concatenate(result, step); + } + } else { + /* + * Non-linear transform found. If it is backed by a `ResidualGrid` and the specified domain + * contains the full `ResidualGrid` domain, then we consider that `step` is approximated by + * an identity transform. Otherwise this method can not process `gridToCRS`. + */ + if (step instanceof InterpolatedTransform) { + final DatumShiftGrid<?,?> grid = ((InterpolatedTransform) step).getShiftGrid(); + if (grid instanceof ResidualGrid) { + GeneralEnvelope bounds = new GeneralEnvelope(new double[size.length], ArraysExt.copyAsDoubles(size)); + if (result != null) { + bounds = Envelopes.transform(result, bounds); + } + for (int i=0; i<size.length; i++) { + if (bounds.getMinimum(i) > 0 || bounds.getMaximum(i) < grid.getGridSize(i)) { + return null; + } + } + continue; + } + } + return null; + } + } + return (LinearTransform) result; + } + + /** * Returns {@code true} if the given object is a grid containing the same data than this grid. */ @Override diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java index f1115b6..9f0de32 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java @@ -97,6 +97,8 @@ public abstract class DatumShiftTransform extends AbstractMathTransform implemen /** * The grid of datum shifts from source datum to target datum, or {@code null} if none. + * + * @see InterpolatedTransform#getShiftGrid() */ final DatumShiftGrid<?,?> grid; diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java index 6e736fb..53800ce 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java @@ -68,7 +68,7 @@ import org.apache.sis.internal.referencing.DirectPositionView; * @author Martin Desruisseaux (IRD, Geomatys) * @author Simon Reynard (Geomatys) * @author Rueben Schulz (UBC) - * @version 1.0 + * @version 1.1 * * @see DatumShiftGrid * @see org.apache.sis.referencing.operation.builder.LocalizationGridBuilder @@ -232,6 +232,17 @@ public class InterpolatedTransform extends DatumShiftTransform { } /** + * Returns the grid of datum shifts specified at construction time. + * + * @return the grid of datum shifts from source to target datum. + * + * @since 1.1 + */ + public final DatumShiftGrid<?,?> getShiftGrid() { + return grid; + } + + /** * Returns the number of input dimensions. * This fixed to {@link DatumShiftGrid#getTranslationDimensions()}. * 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 0a4ff0b..4cee9c2 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 @@ -24,8 +24,10 @@ import java.awt.geom.AffineTransform; import org.opengis.util.FactoryException; import org.opengis.geometry.DirectPosition; import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.TransformException; import org.apache.sis.referencing.operation.matrix.Matrix3; import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.referencing.operation.HardCodedConversions; import org.apache.sis.geometry.DirectPosition1D; import org.apache.sis.geometry.DirectPosition2D; import org.apache.sis.test.DependsOnMethod; @@ -411,6 +413,41 @@ public final strictfp class LinearTransformBuilderTest extends TestCase { } /** + * Tests {@link LinearTransformBuilder#setControlPoints(MathTransform)}. + * This test uses the Mercator projection as a source of non-linear transform + * and verify that we can get a linear approximation from it for a small region. + * + * @throws TransformException if an error occurred during map projection. + * @throws FactoryException if the transform can not be created. + */ + @Test + public void testSetPointsFromTransform() throws TransformException, FactoryException { + final LinearTransformBuilder builder = new LinearTransformBuilder(3, 5); + builder.setControlPoints(HardCodedConversions.mercator().getConversionFromBase().getMathTransform()); + assertPointEquals(builder, 0, 0, 0, 0); + assertPointEquals(builder, 1, 0, 111319, 0); + assertPointEquals(builder, 2, 0, 222639, 0); + assertPointEquals(builder, 2, 1, 222639, 110580); + assertPointEquals(builder, 2, 2, 222639, 221194); + assertPointEquals(builder, 1, 3, 111319, 331877); + assertPointEquals(builder, 1, 4, 111319, 442662); + final Matrix m = builder.create(null).getMatrix(); + assertMatrixEquals("linear", + new Matrix3(111319, 0, 0, + 0, 110662, -62, + 0, 0, 1), m, 0.5); + } + + /** + * Asserts that the builder contains the expected target coordinates for a given source grid coordinates. + */ + private static void assertPointEquals(final LinearTransformBuilder builder, + final int gridX, final int gridY, final int expectedX, final int expectedY) + { + assertArrayEquals(new double[] {expectedX, expectedY}, builder.getControlPoint(new int[] {gridX,gridY}), 0.5); + } + + /** * Tests the effect of {@link LinearTransformBuilder#addLinearizers(Map, int...)}. * * @throws FactoryException if the transform can not be created. 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 803cefa..0d10365 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 @@ -19,9 +19,13 @@ package org.apache.sis.referencing.operation.builder; import java.awt.geom.Point2D; import java.awt.geom.AffineTransform; import org.opengis.util.FactoryException; +import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.opengis.test.referencing.TransformTestCase; +import org.apache.sis.referencing.operation.HardCodedConversions; +import org.apache.sis.referencing.operation.transform.LinearTransform; import org.apache.sis.geometry.Envelope2D; +import org.apache.sis.referencing.operation.matrix.Matrix3; import org.apache.sis.test.DependsOn; import org.junit.Test; @@ -32,7 +36,7 @@ import static org.apache.sis.test.ReferencingAssert.*; * Tests {@link LocalizationGridBuilder}. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.1 * @since 0.8 * @module */ @@ -146,4 +150,36 @@ public final strictfp class LocalizationGridBuilderTest extends TransformTestCas assertArrayEquals(new double[] {-8.5, -123.7}, builder.getRow(1, 2).doubleValues(), STRICT); assertArrayEquals(new double[] {-21.7, -26.2, -123.7}, builder.getColumn(1, 1).doubleValues(), STRICT); } + + /** + * Tests {@link LinearTransformBuilder#approximate(MathTransform, int...)} on a transform created by + * {@link LocalizationGridBuilder}. We verify that the {@link ResidualGrid#approximate(MathTransform, int[])} + * short path is executed. + * + * @throws TransformException if an error occurred while computing test points. + * @throws FactoryException if an error occurred while computing the localization grid. + * + * @see LinearTransformBuilderTest#testSetPointsFromTransform() + */ + @Test + public void testApproximate() throws TransformException, FactoryException { + // Same set of points than `LinearTransformBuilderTest.testSetPointsFromTransform()`. + final int[] gridSize = {3, 5}; + final LinearTransformBuilder points = new LinearTransformBuilder(gridSize); + points.setControlPoints(HardCodedConversions.mercator().getConversionFromBase().getMathTransform()); + + // Non-linear transform producing the same values than the set of points; + final LocalizationGridBuilder builder = new LocalizationGridBuilder(points); + transform = builder.create(null); + assertFalse(transform instanceof LinearTransform); + + // Linear approximation by Least Square Root method. + final LinearTransform linear = LinearTransformBuilder.approximate(transform, gridSize); + org.opengis.test.Assert.assertMatrixEquals("linear", + new Matrix3(111319, 0, 0, + 0, 110662, -62, + 0, 0, 1), linear.getMatrix(), 0.5); + + assertSame("Should have extracted the existing instance instead of computing a new one.", points.create(null), linear); + } } diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java b/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java index 4167311..adb4433 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java +++ b/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java @@ -1863,6 +1863,24 @@ public final class ArraysExt extends Static { } /** + * Returns a copy of the given array where each value has been casted to the {@code double} type. + * + * @param data the array to copy, or {@code null}. + * @return a copy of the given array with values casted to the {@code double} type, + * or {@code null} if the given array was null. + * + * @since 1.1 + */ + public static double[] copyAsDoubles(final int[] data) { + if (data == null) return null; + final double[] result = new double[data.length]; + for (int i=0; i<data.length; i++) { + result[i] = data[i]; + } + return result; + } + + /** * Returns a copy of the given array where each value has been casted to the {@code float} type. * This method does not verify if the casts would cause data loss. *
