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 307f3cf Add more tests for ResamplingGrid.
307f3cf is described below
commit 307f3cf8c2d48068811ff51270249bbd2fd98bf5
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Apr 4 23:20:24 2020 +0200
Add more tests for ResamplingGrid.
---
.../java/org/apache/sis/image/ResamplingGrid.java | 166 +++++++++++---------
.../sis/coverage/grid/PixelTranslationTest.java | 14 +-
.../org/apache/sis/image/ResamplingGridTest.java | 167 ++++++++++++++++++++-
.../operation/HardCodedConversions.java | 28 ++++
4 files changed, 290 insertions(+), 85 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java
index 4f6df17..9af5fa7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java
@@ -29,6 +29,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.io.wkt.Formatter;
import static java.lang.Math.abs;
import static java.lang.Math.rint;
@@ -39,15 +40,17 @@ import static java.lang.Math.rint;
* image resampling operations for avoiding to project the coordinates of every pixels
* when a bilinear interpolation between nearby pixels would be sufficient. Coordinate
* conversions applied by this class are from <em>target</em> grid cell <em>centers</em>
- * to <cite>source</cite> grid cell centers. Despite providing conversions between
cell
- * centers, the constructor expects a {@link MathTransform2D} mapping cell corners.
+ * to <cite>source</cite> grid cell centers.
*
* <p>{@code ResamplingGrid} operates on a delimited space specified by a {@link Rectangle}.
* This space is subdivided into "tiles" (not necessarily coincident with image tiles) where
* each tile provides its own coefficients for bilinear interpolations.
* All coordinates inside the same tile are interpolated using the same coefficients.</p>
*
- * <p>{@code ResamplingGrid} does not support the {@link #inverse()} operation.</p>
+ * <p>{@link ResampledImage} implements {@link MathTransform2D} for allowing usage
by {@link ResampledImage}
+ * but is not a full featured transform. For example it does not support the {@link #inverse()}
operation.
+ * For this reason this class should not be public and instance of this class should not
be accessible
+ * outside {@link ResampledImage}.</p>
*
* @author Martin Desruisseaux (Geomatys)
* @author Remi Marechal (Geomatys)
@@ -72,7 +75,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* This is the maximal difference allowed between a coordinate transformed
* using the original transform and the same coordinate transformed using this grid.
*/
- private static final double TOLERANCE = 0.25;
+ static final double TOLERANCE = 0.125;
/**
* A small tolerance factor for comparisons of floating point numbers. We use the smallest
@@ -86,21 +89,23 @@ final class ResamplingGrid extends AbstractMathTransform2D {
static final double EPS = 1.1920929E-7;
/**
- * The (x,y) coordinates of the upper-left corner in the source grid.
+ * Number of tiles in this grid. A {@link ResamplingGrid} tile is a rectangular region
inside which
+ * bilinear interpolations can be used with acceptable errors. {@link ResamplingGrid}
tiles are not
+ * necessarily coincident with image tiles.
*/
- private final double xmin, ymin;
+ final int numXTiles, numYTiles;
/**
- * Number of pixels in a tile row or column. A {@link ResamplingGrid} tile is a region
inside which
- * bilinear interpolations can be used with acceptable errors. {@link ResamplingGrid}
tiles are not
- * necessarily coincident with image tiles.
+ * Number of pixels in a tile row or column.
+ * Those values are integers, but stored as {@code double} values for avoiding type conversions.
*/
private final double tileWidth, tileHeight;
/**
- * Number of tiles in this grid.
+ * The (x,y) coordinates of the pixel in the upper-left corner of the source grid.
+ * Those values are integers, but stored as {@code double} values for avoiding type conversions.
*/
- private final int numXTiles, numYTiles;
+ private final double xmin, ymin;
/**
* Sequence of (x,y) grid coordinates for all tiles in this grid, stored in row-major
fashion.
@@ -115,18 +120,18 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* <i>etc.</i> with recursive splits like a QuadTree.
*
* <p>Determining an optimal value of {@code depth} argument is the most tricky
part of this class.
- * This work is done by {@link #create(MathTransform2D, Rectangle)} which expects the
first two arguments
- * and compute the third one.</p>
+ * This work is done by {@link #create(MathTransform2D, MathTransform2D, Rectangle)}
which expects
+ * the {@code toSourceCenter} and {@code domain} arguments and compute the {@code depth}
one.</p>
*
- * @param toSource conversion from target cell corners to source cell corners.
- * @param domain the target coordinates for which to create a grid of source coordinates.
- * @param depth number of recursive divisions by 2.
+ * @param toSourceCenter conversion from target cell centers to source cell centers.
+ * @param bounds pixel coordinates of target images for which to create a grid of source
coordinates.
+ * @param depth number of recursive divisions by 2.
*/
- ResamplingGrid(MathTransform2D toSource, final Rectangle domain, final Dimension depth)
throws TransformException {
- this.xmin = domain.x;
- this.ymin = domain.y;
- tileWidth = Math.scalb(domain.width, -depth.width);
- tileHeight = Math.scalb(domain.height, -depth.height);
+ ResamplingGrid(MathTransform2D toSourceCenter, final Rectangle bounds, final Dimension
depth) throws TransformException {
+ this.xmin = bounds.x;
+ this.ymin = bounds.y;
+ tileWidth = Math.scalb(bounds.width, -depth.width);
+ tileHeight = Math.scalb(bounds.height, -depth.height);
numXTiles = 1 << depth.width;
numYTiles = 1 << depth.height;
coordinates = new double[(numXTiles+1) * (numYTiles+1) * DIMENSION];
@@ -137,8 +142,8 @@ final class ResamplingGrid extends AbstractMathTransform2D {
coordinates[p++] = y;
}
}
- toSource = MathTransforms.concatenate(new AffineTransform2D(tileWidth, 0, 0, tileHeight,
xmin + 0.5, ymin + 0.5), toSource);
- toSource.transform(coordinates, 0, coordinates, 0, p/DIMENSION);
+ toSourceCenter = MathTransforms.concatenate(new AffineTransform2D(tileWidth, 0, 0,
tileHeight, xmin, ymin), toSourceCenter);
+ toSourceCenter.transform(coordinates, 0, coordinates, 0, p/DIMENSION);
}
/**
@@ -205,23 +210,27 @@ final class ResamplingGrid extends AbstractMathTransform2D {
/**
* Creates a grid for the given domain of validity.
*
- * @param transform transform from target grid corner to source grid corner.
- * @param domain the domain of validity in source coordinates.
+ * @param toSourceCenter transform from target grid center to source grid center.
+ * @param toSourceCorner transform from target grid corner to source grid corner.
+ * @param bounds the domain of validity in source coordinates.
* @return a precomputed grid for the given transform.
* @throws TransformException if a derivative can not be computed or a point can not
be transformed.
* @throws ImagingOpException if the grid would be too big for being useful.
*/
- static MathTransform2D create(final MathTransform2D transform, final Rectangle domain)
throws TransformException {
- final double xmin = domain.getMinX();
- final double xmax = domain.getMaxX();
- final double ymin = domain.getMinY();
- final double ymax = domain.getMaxY();
+ static MathTransform2D create(final MathTransform2D toSourceCenter,
+ final MathTransform2D toSourceCorner,
+ final Rectangle bounds) throws TransformException
+ {
+ final double xmin = bounds.getMinX();
+ final double xmax = bounds.getMaxX();
+ final double ymin = bounds.getMinY();
+ final double ymax = bounds.getMaxY();
final Point2D.Double point = new Point2D.Double(); // Multi-purpose
buffer.
final Matrix2 upperLeft, upperRight, lowerLeft, lowerRight;
- point.x = xmin; point.y = ymax; upperLeft = derivative(transform, point);
- point.x = xmax; point.y = ymax; upperRight = derivative(transform, point);
- point.x = xmin; point.y = ymin; lowerLeft = derivative(transform, point);
- point.x = xmax; point.y = ymin; lowerRight = derivative(transform, point);
+ point.x = xmin; point.y = ymax; upperLeft = derivative(toSourceCorner, point);
+ point.x = xmax; point.y = ymax; upperRight = derivative(toSourceCorner, point);
+ point.x = xmin; point.y = ymin; lowerLeft = derivative(toSourceCorner, point);
+ point.x = xmax; point.y = ymin; lowerRight = derivative(toSourceCorner, point);
/*
* The tolerance factor is scaled as below. This comment describes a one-dimensional
* case, but the two dimensional case works on the same principle.
@@ -258,7 +267,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* The (m₃ − m₁) value is the maximal difference to be accepted
* in the coefficients of the derivative matrix to be compared.
*/
- final Dimension depth = depth(transform, point,
+ final Dimension depth = depth(toSourceCorner, point,
new Point2D.Double(2 * TOLERANCE / (xmax - xmin),
2 * TOLERANCE / (ymax - ymin)),
xmin, xmax, ymin, ymax, upperLeft, upperRight, lowerLeft, lowerRight);
@@ -270,17 +279,17 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* and some map projection implementations use approximation derived from spherical
formulas.
* The difference is big enough for causing test failure.
*/
- final double xcnt = domain.getCenterX();
- final double ycnt = domain.getCenterY();
+ final double xcnt = bounds.getCenterX();
+ final double ycnt = bounds.getCenterY();
double m00, m10, m01, m11;
Point2D p;
- point.x=xmax; point.y=ycnt; p=transform.transform(point, point); m00 = p.getX();
m10 = p.getY();
- point.x=xmin; point.y=ycnt; p=transform.transform(point, point); m00 -= p.getX();
m10 -= p.getY();
- point.x=xcnt; point.y=ymax; p=transform.transform(point, point); m01 = p.getX();
m11 = p.getY();
- point.x=xcnt; point.y=ymin; p=transform.transform(point, point); m01 -= p.getX();
m11 -= p.getY();
- point.x=xcnt; point.y=ycnt; p=transform.transform(point, point);
- final double width = domain.getWidth();
- final double height = domain.getHeight();
+ point.x=xmax; point.y=ycnt; p=toSourceCenter.transform(point, point); m00 =
p.getX(); m10 = p.getY();
+ point.x=xmin; point.y=ycnt; p=toSourceCenter.transform(point, point); m00 -=
p.getX(); m10 -= p.getY();
+ point.x=xcnt; point.y=ymax; p=toSourceCenter.transform(point, point); m01 =
p.getX(); m11 = p.getY();
+ point.x=xcnt; point.y=ymin; p=toSourceCenter.transform(point, point); m01 -=
p.getX(); m11 -= p.getY();
+ point.x=xcnt; point.y=ycnt; p=toSourceCenter.transform(point, point);
+ final double width = bounds.getWidth();
+ final double height = bounds.getHeight();
final AffineTransform tr = new AffineTransform(m00 / width, m10 / width,
m01 / height, m11 / height,
p.getX(), p.getY());
@@ -292,7 +301,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* Non-affine transform. Create a grid using the cell size computed (indirectly)
* by the `depth(…)` method.
*/
- return new ResamplingGrid(transform, domain, depth);
+ return new ResamplingGrid(toSourceCenter, bounds, depth);
}
/**
@@ -307,25 +316,25 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* <li><i>etc.</i></li>
* </ul>
*
- * @param transform the transform for which to compute the depth.
- * @param point any {@code Point2D.Double} instance, to be written by this method.
- * This is provided in argument only for reducing object allocations.
- * @param tolerance the tolerance value to use in comparisons of matrix coefficients,
- * along the X axis and along the Y axis. The distance between the
location
- * of the matrix being compared is half the size of the region of
interest.
- * @param xmin the minimal <var>x</var> ordinate.
- * @param xmax the maximal <var>x</var> ordinate.
- * @param ymin the minimal <var>y</var> ordinate.
- * @param ymax the maximal <var>y</var> ordinate.
- * @param upperLeft the transform derivative at {@code (xmin,ymax)}.
- * @param upperRight the transform derivative at {@code (xmax,ymax)}.
- * @param lowerLeft the transform derivative at {@code (xmin,ymin)}.
- * @param lowerRight the transform derivative at {@code (xmax,ymin)}.
+ * @param toSourceCorner the transform for which to compute the depth.
+ * @param point any {@code Point2D.Double} instance, to be written by this
method.
+ * This is provided in argument only for reducing object allocations.
+ * @param tolerance the tolerance value to use in comparisons of matrix coefficients,
+ * along the X axis and along the Y axis. The distance between
the location
+ * of the matrix being compared is half the size of the region
of interest.
+ * @param xmin the minimal <var>x</var> ordinate.
+ * @param xmax the maximal <var>x</var> ordinate.
+ * @param ymin the minimal <var>y</var> ordinate.
+ * @param ymax the maximal <var>y</var> ordinate.
+ * @param upperLeft the transform derivative at {@code (xmin,ymax)}.
+ * @param upperRight the transform derivative at {@code (xmax,ymax)}.
+ * @param lowerLeft the transform derivative at {@code (xmin,ymin)}.
+ * @param lowerRight the transform derivative at {@code (xmax,ymin)}.
* @return the number of subdivision along each axis.
* @throws TransformException if a derivative can not be computed.
* @throws ImagingOpException if the grid would be too big for being useful.
*/
- private static Dimension depth(final MathTransform2D transform,
+ private static Dimension depth(final MathTransform2D toSourceCorner,
final Point2D.Double point,
final Point2D.Double tolerance,
final double xmin, final double xmax,
@@ -349,11 +358,11 @@ final class ResamplingGrid extends AbstractMathTransform2D {
tolerance.y *= 2;
final double centerX = point.x = 0.5 * (xmin + xmax);
final double centerY = point.y = 0.5 * (ymin + ymax);
- final Matrix2 center = Matrix2.castOrCopy(transform.derivative(point));
- point.x = xmin; point.y = centerY; final Matrix2 centerLeft = derivative(transform,
point);
- point.x = xmax; point.y = centerY; final Matrix2 centerRight = derivative(transform,
point);
- point.x = centerX; point.y = ymin; final Matrix2 centerLower = derivative(transform,
point);
- point.x = centerX; point.y = ymax; final Matrix2 centerUpper = derivative(transform,
point);
+ final Matrix2 center = Matrix2.castOrCopy(toSourceCorner.derivative(point));
+ point.x = xmin; point.y = centerY; final Matrix2 centerLeft = derivative(toSourceCorner,
point);
+ point.x = xmax; point.y = centerY; final Matrix2 centerRight = derivative(toSourceCorner,
point);
+ point.x = centerX; point.y = ymin; final Matrix2 centerLower = derivative(toSourceCorner,
point);
+ point.x = centerX; point.y = ymax; final Matrix2 centerUpper = derivative(toSourceCorner,
point);
final boolean cl = equals(center, centerLeft, tolerance);
final boolean cr = equals(center, centerRight, tolerance);
final boolean cb = equals(center, centerLower, tolerance);
@@ -365,7 +374,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* centerLeft ├──────┼─ center
*/
if (!((cl & cu) && equals(center, upperLeft, tolerance))) {
- final Dimension depth = depth(transform, point, tolerance, xmin, centerX, centerY,
ymax,
+ final Dimension depth = depth(toSourceCorner, point, tolerance, xmin, centerX,
centerY, ymax,
upperLeft, centerUpper, centerLeft, center);
incrementNonAffineDimension(cl, cu, depth);
nx = depth.width;
@@ -377,7 +386,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* center ─┼──────┤ centerRight
*/
if (!((cr & cu) && equals(center, upperRight, tolerance))) {
- final Dimension depth = depth(transform, point, tolerance, centerX, xmax, centerY,
ymax,
+ final Dimension depth = depth(toSourceCorner, point, tolerance, centerX, xmax,
centerY, ymax,
centerUpper, upperRight, center, centerRight);
incrementNonAffineDimension(cr, cu, depth);
nx = Math.max(nx, depth.width);
@@ -389,7 +398,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* lowerLeft └──────┴─ centerLower
*/
if (!((cl & cb) && equals(center, lowerLeft, tolerance))) {
- final Dimension depth = depth(transform, point, tolerance, xmin, centerX, ymin,
centerY,
+ final Dimension depth = depth(toSourceCorner, point, tolerance, xmin, centerX,
ymin, centerY,
centerLeft, center, lowerLeft, centerLower);
incrementNonAffineDimension(cl, cb, depth);
nx = Math.max(nx, depth.width);
@@ -401,7 +410,7 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* centerLower ─┴──────┘ lowerRight
*/
if (!((cr & cb) && equals(center, lowerRight, tolerance))) {
- final Dimension depth = depth(transform, point, tolerance, centerX, xmax, ymin,
centerY,
+ final Dimension depth = depth(toSourceCorner, point, tolerance, centerX, xmax,
ymin, centerY,
center, centerRight, centerLower, lowerRight);
incrementNonAffineDimension(cr, cb, depth);
nx = Math.max(nx, depth.width);
@@ -457,13 +466,13 @@ final class ResamplingGrid extends AbstractMathTransform2D {
* <p>In Apache SIS implementations, matrices returned by {@code derivative(Point2D)}
methods are already
* instances of {@link Matrix2}. Consequently in most cases this method will just cast
the result.</p>
*
- * @param transform the transform for which to compute the derivative.
- * @param point the location where to compute the derivative.
+ * @param toSourceCenter the transform for which to compute the derivative.
+ * @param point the location where to compute the derivative.
* @return the derivative at the given location as a 2×2 matrix.
* @throws TransformException if the derivative can not be computed.
*/
- private static Matrix2 derivative(final MathTransform2D transform, final Point2D point)
throws TransformException {
- return Matrix2.castOrCopy(transform.derivative(point));
+ private static Matrix2 derivative(final MathTransform2D toSourceCenter, final Point2D
point) throws TransformException {
+ return Matrix2.castOrCopy(toSourceCenter.derivative(point));
}
/**
@@ -505,4 +514,15 @@ final class ResamplingGrid extends AbstractMathTransform2D {
}
}
}
+
+ /**
+ * Formats a pseudo-WKT representation of this transform for debugging purpose.
+ */
+ @Override
+ protected String formatTo(final Formatter formatter) {
+ formatter.append(numXTiles);
+ formatter.append(numYTiles);
+ formatter.setInvalidWKT(ResamplingGrid.class, null);
+ return "ResamplingGrid";
+ }
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
index e4498f3..994cb2b 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
@@ -62,19 +62,19 @@ public final strictfp class PixelTranslationTest extends TestCase {
*/
@Test
public void testTranslatePixelInCell() throws TransformException {
- final MathTransform mt = centerToCorner(3);
+ final MathTransform gridToCRS = centerToCorner(3);
assertMatrixEquals("center → corner", new Matrix4(
1, 0, 0, -0.5,
0, 1, 0, -0.5,
0, 0, 1, -0.5,
- 0, 0, 0, 1), MathTransforms.getMatrix(mt), STRICT);
+ 0, 0, 0, 1), MathTransforms.getMatrix(gridToCRS), STRICT);
/*
* Just for making clear what we explained in javadoc comment: the real world (0,0,0)
coordinates was in the center
* of cell (0,0,0). After we switched to "cell corner" convention, that center is
(½,½,½) in grid coordinates but
* should still map (0,0,0) in "real world" coordinates.
*/
final double[] coordinates = new double[] {0.5, 0.5, 0.5};
- mt.transform(coordinates, 0, coordinates, 0, 1);
+ gridToCRS.transform(coordinates, 0, coordinates, 0, 1);
assertArrayEquals(new double[3], coordinates, STRICT);
}
@@ -84,18 +84,18 @@ public final strictfp class PixelTranslationTest extends TestCase {
*/
@Test
public void testTranslatePixelOrientation() {
- MathTransform mt = centerToCorner2D();
+ MathTransform gridToCRS = centerToCorner2D();
assertMatrixEquals("center → corner", new Matrix3(
1, 0, -0.5,
0, 1, -0.5,
- 0, 0, 1), MathTransforms.getMatrix(mt), STRICT);
+ 0, 0, 1), MathTransforms.getMatrix(gridToCRS), STRICT);
- mt = PixelTranslation.translate(MathTransforms.identity(3), PixelOrientation.LOWER_LEFT,
PixelOrientation.CENTER, 1, 2);
+ gridToCRS = PixelTranslation.translate(MathTransforms.identity(3), PixelOrientation.LOWER_LEFT,
PixelOrientation.CENTER, 1, 2);
assertMatrixEquals("corner → center", new Matrix4(
1, 0, 0, 0.0,
0, 1, 0, +0.5,
0, 0, 1, -0.5,
- 0, 0, 0, 1), MathTransforms.getMatrix(mt), STRICT);
+ 0, 0, 0, 1), MathTransforms.getMatrix(gridToCRS), STRICT);
}
/**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
index dbc3446..6edc12f 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
@@ -16,18 +16,27 @@
*/
package org.apache.sis.image;
+import java.util.Arrays;
import java.util.Random;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import org.apache.sis.geometry.Shapes2D;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.operation.HardCodedConversions;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.io.TableAppender;
+import org.apache.sis.math.Statistics;
import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.TestCase;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.opengis.test.Assert.*;
/**
@@ -61,6 +70,20 @@ public final strictfp class ResamplingGridTest extends TestCase {
}
/**
+ * Tests {@link ResamplingGrid#create(MathTransform2D, MathTransform2D, Rectangle)} with
an affine transform.
+ * The method should detect the affine case and return an equal transform (not necessarily
the same instance).
+ *
+ * @throws TransformException if an error occurred while transforming a coordinate.
+ */
+ @Test
+ public void testCreateAffine() throws TransformException {
+ final Rectangle bounds = new Rectangle(100, 200, 300, 400);
+ final MathTransform2D reference = new AffineTransform2D(2, 0, 0, 3, -10, -20);
+ final MathTransform2D grid = ResamplingGrid.create(reference, centerToCorner(reference),
bounds);
+ assertEquals(reference, grid);
+ }
+
+ /**
* Tests the {@link ResamplingGrid} class using an affine transform.
* Because the transform is linear, results should be identical ignoring rounding errors.
*
@@ -71,7 +94,7 @@ public final strictfp class ResamplingGridTest extends TestCase {
final AffineTransform2D reference = new AffineTransform2D(0.25, 0, 0, 2.5, 4, 2);
final Rectangle bounds = new Rectangle(-7, 3, 12, 8);
final ResamplingGrid grid = new ResamplingGrid(reference, bounds, new Dimension(4,3));
- final Random random = TestUtilities.createRandomNumberGenerator(-854734760285695284L);
+ final Random random = TestUtilities.createRandomNumberGenerator();
final double[] source = new double[2];
final double[] actual = new double[2];
final double[] expected = new double[2];
@@ -79,11 +102,145 @@ public final strictfp class ResamplingGridTest extends TestCase {
source[0] = bounds.x + bounds.width * random.nextDouble();
source[1] = bounds.y + bounds.height * random.nextDouble();
grid.transform(source, 0, actual, 0, 1);
-
- source[0] += 0.5; // Was relative to pixel center, make it relative to
pixel corner.
- source[1] += 0.5;
reference.transform(source, 0, expected, 0, 1);
assertArrayEquals(expected, actual, Numerics.COMPARISON_THRESHOLD);
}
}
+
+ /**
+ * From a transform mapping pixel centers, returns a transform mapping pixel corners.
+ */
+ private static MathTransform2D centerToCorner(final MathTransform projection) {
+ return (MathTransform2D) MathTransforms.concatenate(
+ MathTransforms.translation(-0.5, -0.5), projection,
+ MathTransforms.translation(+0.5, +0.5));
+ }
+
+ /**
+ * Creates a transform for coordinates from the given source region to the given target
region.
+ */
+ private static AffineTransform2D affine(final Rectangle2D source, final Rectangle2D target)
{
+ final double scaleX = target.getWidth() / source.getWidth();
+ final double scaleY = target.getHeight() / source.getHeight();
+ return new AffineTransform2D(scaleX, 0, 0, scaleY,
+ target.getMinX() - source.getMinX() * scaleX,
+ target.getMinY() - source.getMinY() * scaleY);
+ }
+
+ /**
+ * Compares the result of a {@link ResamplingGrid} with the result of a reference transform.
+ *
+ * @param projection a non-linear map projection to use for creating the resampling
grid.
+ * @param domain the domain in source coordinates of the given projection.
+ * @return the {@link ResamplingGrid} created by this method.
+ * @throws TransformException if an error occurred while transforming a coordinate.
+ */
+ private static MathTransform2D compare(final MathTransform projection, final Rectangle2D
domain) throws TransformException {
+ final Rectangle bounds = new Rectangle(10, 20, 800, 600);
+ final MathTransform2D reference = (MathTransform2D) MathTransforms.concatenate(
+ affine(bounds, domain), projection,
+ affine(Shapes2D.transform((MathTransform2D) projection, domain, null), bounds));
+
+ final MathTransform2D grid = ResamplingGrid.create(reference, centerToCorner(reference),
bounds);
+ final Statistics sx = new Statistics("sx");
+ final Statistics sy = new Statistics("sy");
+ final double[] source = new double[2];
+ final double[] actual = new double[2];
+ final double[] expected = new double[2];
+ final int xmin = bounds.x;
+ final int ymin = bounds.y;
+ final int xmax = bounds.width + xmin;
+ final int ymax = bounds.height + ymin;
+ for (int y=ymin; y<ymax; y++) {
+ for (int x=xmin; x<xmax; x++) {
+ source[0] = x;
+ source[1] = y;
+ grid.transform(source, 0, actual, 0, 1);
+ reference.transform(source, 0, expected, 0, 1);
+ final double dx = StrictMath.abs(expected[0] - actual[0]);
+ final double dy = StrictMath.abs(expected[1] - actual[1]);
+ if (!(dx <= ResamplingGrid.TOLERANCE && dy <= ResamplingGrid.TOLERANCE))
{
+ fail("Error at (" + x + ',' + y + "): expected " +
+ Arrays.toString(expected) + " but got " +
+ Arrays.toString(actual) + ". Error is (" + dx + ", " + dy + ')');
+ }
+ sx.accept(dx);
+ sy.accept(dy);
+ }
+ }
+ if (VERBOSE) {
+ // Print a summary of errors.
+ final TableAppender table = new TableAppender();
+ table.setMultiLinesCells(true);
+ table.appendHorizontalSeparator();
+ table.append(sx.toString());
+ table.nextColumn();
+ table.append(sy.toString());
+ table.nextLine();
+ table.appendHorizontalSeparator();
+ out.println(table);
+ out.println();
+ }
+ return grid;
+ }
+
+ /**
+ * Tests {@link ResamplingGrid} with the Mercator projection on a region crossing the
equator.
+ *
+ * @throws TransformException if an error occurred while transforming a coordinate.
+ */
+ @Test
+ public void testMercator() throws TransformException {
+ final MathTransform projection = HardCodedConversions.mercator().getConversionFromBase().getMathTransform();
+ final Rectangle domain = new Rectangle(-20, -40, 40, 80);
+ final MathTransform2D tr = compare(projection, domain);
+ assertInstanceOf("Expected a non-linear transform.", ResamplingGrid.class, tr);
+ final ResamplingGrid grid = (ResamplingGrid) tr;
+ assertEquals("The x dimension should be affine.", 1, grid.numXTiles);
+ assertEquals("The y dimension can not be affine.", 32, grid.numYTiles); // Empirical
value.
+ }
+
+ /**
+ * Tests {@link ResamplingGrid} with the Mercator projection on a region
+ * small enough for being wholly approximated by a single affine transform.
+ *
+ * @throws TransformException if an error occurred while transforming a coordinate.
+ */
+ @Test
+ public void testMercatorOnSmallArea() throws TransformException {
+ final MathTransform projection = HardCodedConversions.mercator().getConversionFromBase().getMathTransform();
+ final Rectangle2D domain = new Rectangle2D.Double(-20, 20, 0.25, 0.25);
+ final MathTransform2D tr = compare(projection, domain);
+ assertInstanceOf("Expected a linear transform.", AffineTransform2D.class, tr);
+ }
+
+ /**
+ * Tests {@link ResamplingGrid} with a Lambert Conic Conformal projection.
+ *
+ * @throws TransformException if an error occurred while transforming a coordinate.
+ */
+ @Test
+ public void testLambert() throws TransformException {
+ final MathTransform projection = HardCodedConversions.lambert().getConversionFromBase().getMathTransform();
+ final Rectangle domain = new Rectangle(-20, 30, 40, 20);
+ final MathTransform2D tr = compare(projection, domain);
+ assertInstanceOf("Expected a non-linear transform.", ResamplingGrid.class, tr);
+ final ResamplingGrid grid = (ResamplingGrid) tr;
+ assertEquals("The x dimension can not be affine.", 32, grid.numXTiles); // Empirical
value.
+ assertEquals("The y dimension can not be affine.", 16, grid.numYTiles); // Empirical
value.
+ }
+
+ /**
+ * Tests {@link ResamplingGrid} with the Lambert Conic Conformal projection on a
+ * region small enough for being wholly approximated by a single affine transform.
+ *
+ * @throws TransformException if an error occurred while transforming a coordinate.
+ */
+ @Test
+ public void testLambertOnSmallArea() throws TransformException {
+ final MathTransform projection = HardCodedConversions.lambert().getConversionFromBase().getMathTransform();
+ final Rectangle2D domain = new Rectangle2D.Double(-20, 50, 0.025, 0.05);
+ final MathTransform2D tr = compare(projection, domain);
+ assertInstanceOf("Expected a linear transform.", AffineTransform2D.class, tr);
+ }
}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
index 469a1d1..48878fc 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
@@ -18,10 +18,12 @@ package org.apache.sis.referencing.operation;
import java.util.Map;
import java.util.Collections;
+import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.operation.OperationMethod;
import org.apache.sis.internal.referencing.provider.Mercator1SP;
+import org.apache.sis.internal.referencing.provider.LambertConformal1SP;
import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.referencing.cs.HardCodedCS;
@@ -48,6 +50,19 @@ public final strictfp class HardCodedConversions {
}
/**
+ * A defining conversion for a <cite>Lambert Conic Conformal (1SP)</cite>
projection
+ * with a Latitude of natural origin arbitrarily set to 40.
+ */
+ public static final DefaultConversion LAMBERT;
+ static {
+ final OperationMethod method = new LambertConformal1SP();
+ final ParameterValueGroup pg = method.getParameters().createValue();
+ pg.parameter("Latitude of natural origin").setValue(40);
+ LAMBERT = new DefaultConversion(Collections.singletonMap(OperationMethod.NAME_KEY,
"Lambert Conic Conformal"),
+ method, null, pg);
+ }
+
+ /**
* Do not allow instantiation of this class.
*/
private HardCodedConversions() {
@@ -95,6 +110,19 @@ public final strictfp class HardCodedConversions {
}
/**
+ * A two-dimensional Lambert Conic Conformal (1SP) projection using the WGS84 datum.
+ * This CRS uses (<var>easting</var>, <var>northing</var>) coordinates
in metres.
+ * The base CRS uses (<var>longitude</var>, <var>latitude</var>)
axes
+ * and the prime meridian is Greenwich.
+ *
+ * @return two-dimensional Lambert Conic Conformal (1SP) projection.
+ */
+ public static DefaultProjectedCRS lambert() {
+ return new DefaultProjectedCRS(name("Lambert Conic Conformal"),
+ HardCodedCRS.WGS84, HardCodedConversions.LAMBERT, HardCodedCS.PROJECTED);
+ }
+
+ /**
* Puts the given name in a property map CRS constructors.
*/
private static Map<String,?> name(final String name) {
|