sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Add more tests for ResamplingGrid.
Date Sat, 04 Apr 2020 21:20:42 GMT
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) {


Mime
View raw message