sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: Add a `GridOrientation` argument to `GridGeometry(GridExtent, Envelope)` constructor. The intent is to make this constructor more useful for expressing "desired" geometry, as opposed to geometry of preexisting data.
Date Wed, 23 Sep 2020 17:23:49 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

commit 3dff7c6c4bad6220d5d7c906a3dac34d74c87547
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Sep 23 16:13:30 2020 +0200

    Add a `GridOrientation` argument to `GridGeometry(GridExtent, Envelope)` constructor.
    The intent is to make this constructor more useful for expressing "desired" geometry,
    as opposed to geometry of preexisting data.
    
    Remove `GridCoverage2D(Envelope, …, RenderedImage)` constructor for consistency with
the idea
    that (Envelope, Extent) tuples should be used for desired instead than actual grid geometries.
    If someone really wants to create a `GridCoverage2D` from such tuple, `GridCoverageBuilder`
    provides better controls.
---
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  57 +----------
 .../sis/coverage/grid/GridCoverageBuilder.java     |   2 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   |  20 ++--
 .../org/apache/sis/coverage/grid/GridGeometry.java |  41 +++++++-
 .../apache/sis/coverage/grid/GridOrientation.java  |  87 +++++++++++++++++
 .../sis/coverage/grid/GridCoverageBuilderTest.java |   4 +-
 .../sis/coverage/grid/GridDerivationTest.java      |   6 +-
 .../apache/sis/coverage/grid/GridExtentTest.java   |  20 +++-
 .../apache/sis/coverage/grid/GridGeometryTest.java | 104 ++++++++++++++++++++-
 .../coverage/grid/ResampledGridCoverageTest.java   |   4 +-
 .../sis/storage/geotiff/GridGeometryBuilder.java   |   3 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |   2 +-
 12 files changed, 267 insertions(+), 83 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index 7f14bd1..fe5df02 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -29,7 +29,6 @@ import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
-import org.opengis.geometry.Envelope;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
@@ -51,7 +50,6 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.Workaround;
 import org.apache.sis.util.Debug;
 
 import static java.lang.Math.min;
@@ -204,6 +202,8 @@ public class GridCoverage2D extends GridCoverage {
      * @throws IllegalGridGeometryException if the {@code domain} does not met the above-documented
conditions.
      * @throws IllegalArgumentException if the image number of bands is not the same than
the number of sample dimensions.
      * @throws ArithmeticException if the distance between grid location and image location
exceeds the {@code long} capacity.
+     *
+     * @see GridCoverageBuilder
      */
     public GridCoverage2D(GridGeometry domain, final List<? extends SampleDimension>
range, RenderedImage data) {
         /*
@@ -304,7 +304,7 @@ public class GridCoverage2D extends GridCoverage {
                 } catch (TransformException e) {
                     throw new IllegalGridGeometryException(e);                  // Should
never happen.
                 } else {
-                    domain = new GridGeometry(extent, domain.envelope);
+                    domain = new GridGeometry(extent, domain.envelope, GridOrientation.HOMOTHETIC);
                 }
             }
         }
@@ -312,57 +312,6 @@ public class GridCoverage2D extends GridCoverage {
     }
 
     /**
-     * Constructs a grid coverage using the specified envelope, range and data.
-     * This convenience constructor computes a {@link GridGeometry} from the given envelope
and image size.
-     * This constructor assumes that all grid axes are in the same order than CRS axes and
no axis is flipped.
-     * This straightforward approach often results in the <var>y</var> axis to
be oriented toward up,
-     * not down as commonly expected with rendered images.
-     *
-     * <p>This constructor is generally not recommended because of the assumptions
on axis order and directions.
-     * For better control, use the constructor expecting a {@link GridGeometry} argument
instead.
-     * This constructor is provided mostly as a convenience for testing purposes.</p>
-     *
-     * @param  domain  the envelope encompassing all images, from upper-left corner to lower-right
corner.
-     *                 If {@code null} a default grid geometry will be created with no CRS
and identity conversion.
-     * @param  range   sample dimensions for each image band. The size of this list must
be equal to the number of bands.
-     *                 If {@code null}, default sample dimensions will be created with no
transfer function.
-     * @param  data    the sample values as a {@link RenderedImage}, with one band for each
sample dimension.
-     * @throws IllegalArgumentException if the image number of bands is not the same than
the number of sample dimensions.
-     *
-     * @see GridGeometry#GridGeometry(GridExtent, Envelope)
-     */
-    public GridCoverage2D(final Envelope domain, final List<? extends SampleDimension>
range, final RenderedImage data) {
-        super(createGridGeometry(data, domain), defaultIfAbsent(range, data, ImageUtilities.getNumBands(data)));
-        this.data = data;   // Non-null verified by createGridGeometry(…, data).
-        xDimension   = 0;
-        yDimension   = 1;
-        gridToImageX = 0;
-        gridToImageY = 0;
-        verifyBandCount(range, data);
-        gridGeometry2D = new AtomicReference<>();
-    }
-
-    /**
-     * Creates a grid geometry from an envelope. The grid extent is computed from the image
size.
-     * This static method is a workaround for RFE #4093999
-     * ("Relax constraint on placement of this()/super() call in constructors").
-     */
-    @Workaround(library="JDK", version="1.8")
-    private static GridGeometry createGridGeometry(final RenderedImage data, final Envelope
envelope) {
-        ArgumentChecks.ensureNonNull("data", data);
-        CoordinateReferenceSystem crs = null;
-        int dimension = BIDIMENSIONAL;
-        if (envelope != null) {
-            dimension = envelope.getDimension();
-            if (dimension < BIDIMENSIONAL) {
-                throw new IllegalGridGeometryException(Resources.format(Resources.Keys.GridEnvelopeMustBeNDimensional_1,
BIDIMENSIONAL));
-            }
-            crs = envelope.getCoordinateReferenceSystem();
-        }
-        return new GridGeometry(createExtent(dimension, ImageUtilities.getBounds(data), crs),
envelope);
-    }
-
-    /**
      * Creates a grid extent with the low and high coordinates of the given image bounds.
      * The coordinate reference system is used for extracting grid axis names, in particular
      * the {@link DimensionNameType#VERTICAL} and {@link DimensionNameType#TIME} dimensions.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index b446dfe..c598725 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -234,7 +234,7 @@ public class GridCoverageBuilder {
      * @see GridGeometry#GridGeometry(GridExtent, Envelope)
      */
     public GridCoverageBuilder setDomain(final Envelope domain) {
-        return setDomain(domain == null ? null : new GridGeometry(null, domain));
+        return setDomain(domain == null ? null : new GridGeometry(null, domain, null));
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 513244d..503fff0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -1346,15 +1346,13 @@ public class GridExtent implements GridEnvelope, LenientComparable,
Serializable
     /**
      * Creates an affine transform from the coordinates of this grid to the coordinates of
the given envelope.
      * This method assumes that all axes are in the same order (no axis swapping) and there
is no flipping of
-     * axis direction. The transform maps cell corners.
+     * axis direction except for those specified in the {@code flips} bitmask. The transform
maps cell corners.
      *
-     * <p>This method is not yet public because we have not decided what could be an
API for controlling axis
-     * swapping and flipping, if desired.</p>
-     *
-     * @param  env  the target envelope. Despite this method name, the envelope CRS is ignored.
+     * @param  env    the target envelope. Despite this method name, the envelope CRS is
ignored.
+     * @param  flips  bitmask of axes to flip.
      * @return an affine transform from this grid extent to the given envelope, expressed
as a matrix.
      */
-    final MatrixSIS cornerToCRS(final Envelope env) {
+    final MatrixSIS cornerToCRS(final Envelope env, final long flips) {
         final int          srcDim = getDimension();
         final int          tgtDim = env.getDimension();
         final MatrixSIS    affine = Matrices.create(tgtDim + 1, srcDim + 1, ExtendedPrecisionMatrix.ZERO);
@@ -1362,16 +1360,18 @@ public class GridExtent implements GridEnvelope, LenientComparable,
Serializable
         final DoubleDouble offset = new DoubleDouble();
         for (int j=0; j<tgtDim; j++) {
             if (j < srcDim) {
+                final boolean flip = (flips & Numerics.bitmask(j)) != 0;
                 offset.set(coordinates[j]);
                 scale.set(coordinates[j + srcDim]);
                 scale.subtract(offset);
-                scale.add(1);
-                scale.inverseDivideGuessError(env.getSpan(j));
-                if (!offset.isZero()) {                     // Use `if` for keeping the value
if scale is NaN.
+                scale.add(1);                                   // == getSize(j) but without
overflow.
+                scale.inverseDivideGuessError(env.getSpan(j));  // == (envelope span) / (grid
size).
+                if (flip) scale.negate();
+                if (!offset.isZero()) {                         // Use `if` for keeping the
value if scale is NaN.
                     offset.multiply(scale);
                     offset.negate();
                 }
-                offset.addGuessError(env.getMinimum(j));
+                offset.addGuessError(flip ? env.getMaximum(j) : env.getMinimum(j));
                 affine.setNumber(j, srcDim, offset);
             } else {
                 scale.value = Double.NaN;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 3def0f8..fb91cf6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -61,6 +61,7 @@ import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.NullArgumentException;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
@@ -565,8 +566,36 @@ public class GridGeometry implements LenientComparable, Serializable
{
      * @param  extent    the valid extent of grid coordinates, or {@code null} if unknown.
      * @param  envelope  the envelope together with CRS of the "real world" coordinates,
or {@code null} if unknown.
      * @throws NullPointerException if {@code extent} and {@code envelope} arguments are
both null.
+     *
+     * @deprecated Replaced by {@link #GridGeometry(GridExtent, Envelope, GridOrientation)}.
      */
+    @Deprecated
     public GridGeometry(final GridExtent extent, final Envelope envelope) {
+        this(extent, envelope, GridOrientation.HOMOTHETIC);
+    }
+
+    /**
+     * Creates a grid geometry with an extent and an envelope.
+     * This constructor can be used when the <cite>grid to CRS</cite> transform
is unknown.
+     * If only the coordinate reference system is known, then the envelope coordinates can
be
+     * {@linkplain GeneralEnvelope#isAllNaN() all NaN}.
+     *
+     * <p>The main purpose of this constructor is to create "desired" grid geometries,
for example for use in
+     * {@linkplain org.apache.sis.storage.GridCoverageResource#read(GridGeometry, int...)
read} or
+     * {@linkplain org.apache.sis.coverage.grid.GridCoverageProcessor#resample resample}
operations.
+     * For grid geometries describing preexisting data, it is safer and more flexible to
use one of
+     * the constructors expecting a {@link MathTransform} argument.</p>
+     *
+     * @param  extent       the valid extent of grid coordinates, or {@code null} if unknown.
+     * @param  envelope     the envelope together with CRS of the "real world" coordinates,
or {@code null} if unknown.
+     * @param  orientation  high-level description of desired characteristics of the {@code
gridToCRS} transform.
+     *                      Ignored (can be null) if {@code extent} or {@code envelope} is
null.
+     * @throws NullPointerException if {@code extent} and {@code envelope} arguments are
both null,
+     *         or if only the {@code orientation} argument is null.
+     *
+     * @since 1.1
+     */
+    public GridGeometry(final GridExtent extent, final Envelope envelope, final GridOrientation
orientation) {
         this.extent = extent;
         nonLinears  = 0;
         /*
@@ -576,6 +605,9 @@ public class GridGeometry implements LenientComparable, Serializable {
         boolean nilEnvelope = true;
         final ImmutableEnvelope env = ImmutableEnvelope.castOrCopy(envelope);
         if (env == null || ((nilEnvelope = env.isAllNaN()) && env.getCoordinateReferenceSystem()
== null)) {
+            if (env != null && nilEnvelope) {
+                throw new NullArgumentException(Errors.format(Errors.Keys.UnspecifiedCRS));
+            }
             ArgumentChecks.ensureNonNull("extent", extent);
             this.envelope = null;
         } else {
@@ -584,10 +616,11 @@ public class GridGeometry implements LenientComparable, Serializable
{
                 /*
                  * If we have both the extent and an envelope with at least one non-NaN coordinates,
                  * create the `cornerToCRS` transform. The `gridToCRS` calculation uses the
knowledge
-                 * that all scale factors are on diagonal with no sign reversal, which allows
simpler
-                 * calculation than full matrix multiplication. Use double-double arithmetic
everywhere.
+                 * that all scale factors are on diagonal, which allows simpler calculation
than full
+                 * matrix multiplication. Use double-double arithmetic everywhere.
                  */
-                final MatrixSIS affine = extent.cornerToCRS(env);
+                ArgumentChecks.ensureNonNull("orientation", orientation);
+                final MatrixSIS affine = extent.cornerToCRS(env, orientation.flip);
                 cornerToCRS = MathTransforms.linear(affine);
                 final int srcDim = cornerToCRS.getSourceDimensions();       // Translation
column in matrix.
                 final int tgtDim = cornerToCRS.getTargetDimensions();       // Number of
matrix rows before last row.
@@ -595,7 +628,7 @@ public class GridGeometry implements LenientComparable, Serializable {
                 for (int j=0; j<tgtDim; j++) {
                     final DoubleDouble scale  = (DoubleDouble) affine.getNumber(j, j);
                     final DoubleDouble offset = (DoubleDouble) affine.getNumber(j, srcDim);
-                    resolution[j] = scale.doubleValue();
+                    resolution[j] = Math.abs(scale.doubleValue());
                     scale.multiply(0.5);
                     offset.add(scale);
                     affine.setNumber(j, srcDim, offset);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
new file mode 100644
index 0000000..02b2bd1
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage.grid;
+
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.datum.PixelInCell;
+
+
+/**
+ * High-level description about how a grid is orientated relative to the CRS axes.
+ * This is determined by the {@linkplain GridGeometry#getGridToCRS(PixelInCell) grid to CRS}
transform.
+ * For example conversion from grid coordinates to CRS coordinates may flip the <var>y</var>
axis
+ * (grid coordinates increasing toward down on screen), or may swap <var>x</var>
and <var>y</var> axes, <i>etc.</i>
+ * The possibilities are infinite; this enumeration covers only a few common types.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public enum GridOrientation {
+    /**
+     * The {@code gridToCRS} transform applies only scales and translations (no axis flip
or swap).
+     * Moving along the grid axis in dimension <var>i</var> causes a displacement
along the CRS axis
+     * in the same dimension <var>i</var>. In matrix terms all coefficients on
the diagonal are positives
+     * (restricted to 1 on the last row), the translation terms can be anything, and all
other terms are zero.
+     * For example in the three-dimensional case:
+     *
+     * {@preformat math
+     *   ┌                ┐
+     *   │ Sx  0   0   Tx │
+     *   │ 0   Sy  0   Ty │
+     *   │ 0   0   Sz  Tz │
+     *   │ 0   0   0   1  │
+     *   └                ┘
+     * }
+     *
+     * with
+     * <var>S<sub>x</sub></var> &gt; 0,
+     * <var>S<sub>y</sub></var> &gt; 0 and
+     * <var>S<sub>z</sub></var> &gt; 0.
+     */
+    HOMOTHETIC(0),
+
+    /**
+     * The {@code gridToCRS} transform applies scales and translations with a flip of the
second axis (<var>y</var>).
+     * This is the same kind of conversion than {@link #HOMOTHETIC} except that the <var>S<sub>y</sub></var>
term in
+     * the matrix is replaced by −<var>S<sub>y</sub></var>.
+     *
+     * <p>{@code REFLECTION_Y} is commonly used when the grid is a {@link java.awt.image.RenderedImage}.
+     * By contrast, an {@link #HOMOTHETIC} transform often results in <var>y</var>
axis oriented toward up,
+     * instead of down as commonly expected with rendered images.
+     * This {@code REFLECTION_Y} value matches the common usage for grids backed by images.</p>
+     */
+    REFLECTION_Y(2);
+
+    /*
+     * TODO: add DISPLAY. The difference compared to REFLECTION_Y is that it may change CRS
axes.
+     */
+
+    /**
+     * Bitmask of axes to flip.
+     * This is the argument to give in calls to {@link GridExtent#cornerToCRS(Envelope, long)}.
+     */
+    final int flip;
+
+    /**
+     * Creates a new enumeration.
+     */
+    private GridOrientation(final int flip) {
+        this.flip = flip;
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
index 6a2a7f7..cd5e839 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
@@ -139,7 +139,7 @@ public final strictfp class GridCoverageBuilderTest extends TestCase {
         final GeneralEnvelope env = new GeneralEnvelope(HardCodedCRS.WGS84);
         env.setRange(0, 0, 10);     // Scale factor of 2 for grid size of 10.
         env.setRange(1, 0,  4);     // Scale factor of ½ for grid size of 8.
-        GridGeometry grid = new GridGeometry(new GridExtent(8, 5), env);
+        GridGeometry grid = new GridGeometry(new GridExtent(8, 5), env, GridOrientation.HOMOTHETIC);
         assertSame(builder, builder.setDomain(grid));
         try {
             builder.build();
@@ -147,7 +147,7 @@ public final strictfp class GridCoverageBuilderTest extends TestCase {
         } catch (IllegalStateException ex) {
             assertNotNull(ex.getMessage());
         }
-        grid = new GridGeometry(new GridExtent(width, height), env);
+        grid = new GridGeometry(new GridExtent(width, height), env, GridOrientation.HOMOTHETIC);
         assertSame(builder, builder.setDomain(grid));
         return builder.build();
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
index bc58662..7dd4a08 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -564,8 +564,8 @@ public final strictfp class GridDerivationTest extends TestCase {
         Envelope2D   domain   = new Envelope2D(HardCodedCRS.WGS84, 10, 20, 110, 70);
         Envelope2D   request  = new Envelope2D(HardCodedCRS.WGS84, -5, 25, 100, 90);
         Envelope2D   expected = new Envelope2D(HardCodedCRS.WGS84, 10, 25,  85, 65);
-        GridGeometry grid1    = new GridGeometry(null, domain);
-        GridGeometry grid2    = new GridGeometry(null, request);
+        GridGeometry grid1    = new GridGeometry(null, domain,  null);
+        GridGeometry grid2    = new GridGeometry(null, request, null);
         GridGeometry subgrid  = grid1.derive().subgrid(grid2).build();
         assertTrue(subgrid.isEnvelopeOnly());
         assertEnvelopeEquals(expected, subgrid.getEnvelope(), STRICT);
@@ -575,7 +575,7 @@ public final strictfp class GridDerivationTest extends TestCase {
          */
         request.setCoordinateReferenceSystem(HardCodedCRS.WGS84_φλ);
         request.setRect(25, -5, 90, 100);
-        grid2   = new GridGeometry(null, request);
+        grid2   = new GridGeometry(null, request, null);
         subgrid = grid1.derive().subgrid(grid2).build();
         assertSame(HardCodedCRS.WGS84, subgrid.getCoordinateReferenceSystem());
         assertTrue(subgrid.isEnvelopeOnly());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 21ecebd..a87ffe1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -269,18 +269,32 @@ public final strictfp class GridExtentTest extends TestCase {
     }
 
     /**
-     * Tests {@link GridExtent#cornerToCRS(Envelope)}.
+     * Tests {@link GridExtent#cornerToCRS(Envelope, long)}.
      */
     @Test
     public void testCornerToCRS() {
         final GeneralEnvelope aoi = new GeneralEnvelope(HardCodedCRS.WGS84);
         aoi.setRange(0,  40, 55);
         aoi.setRange(1, -10, 70);
-        final GridExtent extent = new GridExtent(null, new long[] {-20, -25}, new long[]
{10, 15}, false);
+        final GridExtent extent = new GridExtent(null,
+                new long[] {-20, -25},
+                new long[] { 10,  15}, false);
+        /*
+         * No axis flip.
+         * Verification:  y  =  2 × −25 + 40  =  −10  (the minimum value declared in
envelope).
+         */
         assertMatrixEquals("cornerToCRS", new Matrix3(
                 0.5,  0,   50,
                 0,    2,   40,
-                0,    0,    1), extent.cornerToCRS(aoi), STRICT);
+                0,    0,    1), extent.cornerToCRS(aoi, 0), STRICT);
+        /*
+         * Y axis flip.
+         * Verification:  y  =  −2 × −25 + 20  =  70  (the maximum value declared in
envelope).
+         */
+        assertMatrixEquals("cornerToCRS", new Matrix3(
+                0.5,  0,   50,
+                0,   -2,   20,
+                0,    0,    1), extent.cornerToCRS(aoi, 2), STRICT);
     }
 
     /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index b44427e..8ea4d2d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -21,10 +21,12 @@ import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.referencing.operation.matrix.Matrix2;
 import org.apache.sis.referencing.operation.matrix.Matrix3;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -54,6 +56,47 @@ public final strictfp class GridGeometryTest extends TestCase {
     }
 
     /**
+     * Verifies the shift between the two {@code gridToCRS} transforms.
+     * This method should be invoked when the transforms are linear.
+     *
+     * @param  grid  the grid geoemtry to validate.
+     */
+    private static void verifyGridToCRS(final GridGeometry grid) {
+        final Matrix tr1 = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER));
+        final Matrix tr2 = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+        final Matrix m;
+        try {
+            /*
+             * Multiply the matrices instead than concatenating the transforms
+             * for making sure that we do not get cached transforms.
+             */
+            m = MatrixSIS.castOrCopy(tr2).inverse().multiply(tr1);
+        } catch (NoninvertibleTransformException e) {
+            throw new AssertionError(e);
+        }
+        /*
+         * Example of expected matrix (size may vary):
+         * ┌              ┐
+         * │ 1  0  0  0.5 │
+         * │ 0  1  0  0.5 │
+         * │ 0  0  1  0.5 │
+         * │ 0  0  0  1   │
+         * └              ┘
+         */
+        for (int j=m.getNumRow(); --j >=0;) {
+            double expected = 0.5;                          // Expected translation term
in last column.
+            for (int i=m.getNumCol(); --i >= 0;) {
+                if (i == j) expected = 1;                   // Expected value on the diagonal.
+                final double actual = m.getElement(j,i);
+                if (actual != expected) {
+                    fail("Expected " + expected + " but got " + actual + " in following matrix:\n"
+ m);
+                }
+                expected = 0;                               // For all values other than
diagonal and translation.
+            }
+        }
+    }
+
+    /**
      * Tests construction with an identity transform mapping pixel corner.
      */
     @Test
@@ -94,6 +137,7 @@ public final strictfp class GridGeometryTest extends TestCase {
          */
         assertArrayEquals("resolution", new double[] {1, 1, 1, 1}, grid.getResolution(false),
STRICT);
         assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2, 3));
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -137,6 +181,7 @@ public final strictfp class GridGeometryTest extends TestCase {
          */
         assertArrayEquals("resolution", new double[] {1, 1, 1}, grid.getResolution(false),
STRICT);
         assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2));
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -163,6 +208,12 @@ public final strictfp class GridGeometryTest extends TestCase {
                 0, 1, 0, 7,     // Combination of above scales (diagonal) and translation
(last column).
                 0, 0, 3, 8,
                 0, 0, 0, 1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER)),
STRICT);
+        /*
+         * Verify other computed properties.
+         */
+        assertArrayEquals("resolution", new double[] {2, 1, 3}, grid.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2));
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -176,12 +227,14 @@ public final strictfp class GridGeometryTest extends TestCase {
         GridGeometry grid = new GridGeometry(extent, PixelInCell.CELL_CENTER, MathTransforms.identity(2),
HardCodedCRS.WGS84);
         GeneralEnvelope expected = new GeneralEnvelope(new double[] {-0.5, -0.5}, new double[]
{125.5, 196.5});
         assertEnvelopeEquals(expected, grid.getEnvelope(), STRICT);
+        verifyGridToCRS(grid);
         /*
          * Derive a new grid geometry with 10×10 times more cells. The geographic area should
be unchanged.
          */
         extent = extent.resize(1260, 1970);
         grid = grid.derive().resize(extent, 0.1, 0.1).build();
         assertEnvelopeEquals(expected, grid.getEnvelope(), STRICT);
+        verifyGridToCRS(grid);
         /*
          * If we create a grid geometry with identical properties, the envelope computed
by that grid geometry would
          * be different than the envelope computed above if the "grid to CRS" transform is
not correctly adjusted.
@@ -189,6 +242,7 @@ public final strictfp class GridGeometryTest extends TestCase {
         final GridGeometry alternative = new GridGeometry(grid.getExtent(), PixelInCell.CELL_CENTER,
                  grid.getGridToCRS(PixelInCell.CELL_CENTER), grid.getCoordinateReferenceSystem());
         assertEnvelopeEquals(expected, alternative.getEnvelope(), STRICT);
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -207,6 +261,7 @@ public final strictfp class GridGeometryTest extends TestCase {
                 0, 0, 1));
         final GridGeometry grid = new GridGeometry(extent, PixelInCell.CELL_CENTER, identity,
null);
         assertTrue("gridToCRS.isIdentity", grid.getGridToCRS(PixelInCell.CELL_CORNER).isIdentity());
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -262,6 +317,12 @@ public final strictfp class GridGeometryTest extends TestCase {
                 0,   0.5, -89.75,
                 0.5, 0,  -179.75,
                 0,   0,     1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER)),
STRICT);
+        /*
+         * Verify other computed properties.
+         */
+        assertArrayEquals("resolution", new double[] {0.5, 0.5}, grid.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -275,12 +336,39 @@ public final strictfp class GridGeometryTest extends TestCase {
         final GeneralEnvelope aoi = new GeneralEnvelope(HardCodedCRS.WGS84);
         aoi.setRange(0,  40, 55);
         aoi.setRange(1, -10, 70);
-        final GridGeometry grid = new GridGeometry(new GridExtent(null, new long[] {-20,
-25}, new long[] {10, 15}, false), aoi);
-        final Matrix matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+        final GridExtent extent = new GridExtent(null,
+                new long[] {-20, -25},
+                new long[] { 10,  15}, false);
+        /*
+         * Simplest case: no axis flip.
+         * Verification:  y  =  2 × −25 + 40  =  −10  (the minimum value declared in
envelope).
+         */
+        GridGeometry grid = new GridGeometry(extent, aoi, GridOrientation.HOMOTHETIC);
+        Matrix matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
         assertMatrixEquals("cornerToCRS", new Matrix3(
                 0.5,  0,   50,
                 0,    2,   40,
                 0,    0,    1), matrix, STRICT);
+
+        // Verify other computed properties.
+        assertArrayEquals("resolution", new double[] {0.5, 2}, grid.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+        verifyGridToCRS(grid);
+        /*
+         * Same envelope and extent, but flip Y axis.
+         * Verification:  y  =  −2 × −25 + 20  =  70  (the maximum value declared in
envelope).
+         */
+        grid = new GridGeometry(extent, aoi, GridOrientation.REFLECTION_Y);
+        matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+        assertMatrixEquals("cornerToCRS", new Matrix3(
+                0.5,  0,   50,
+                0,   -2,   20,
+                0,    0,    1), matrix, STRICT);
+
+        // Verify other computed properties.
+        assertArrayEquals("resolution", new double[] {0.5, 2}, grid.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -318,6 +406,12 @@ public final strictfp class GridGeometryTest extends TestCase {
         assertMatrixEquals("gridToCRS", new Matrix2(
                   2, 3,
                   0, 1), MathTransforms.getMatrix(reduced.getGridToCRS(PixelInCell.CELL_CORNER)),
STRICT);
+        /*
+         * Verify other computed properties.
+         */
+        assertArrayEquals("resolution", new double[] {0.5, 0.5, 2}, grid.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2));
+        verifyGridToCRS(grid);
     }
 
     /**
@@ -347,6 +441,12 @@ public final strictfp class GridGeometryTest extends TestCase {
                         0.5, 0,  -180,
         //              0,   0,     3,  // All scale coefficients set to 0.
                         0,   0,     1), MathTransforms.getMatrix(tr), STRICT);
+        /*
+         * Verify other computed properties.
+         */
+        assertArrayEquals("resolution", new double[] {0.5, 0.5}, reduced.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", reduced.isConversionLinear(0, 1));
+        verifyGridToCRS(reduced);
     }
 
     /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 60e5328..7fe355e 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -105,7 +105,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
         final int y = random.nextInt(32) - 10;
         final GridGeometry gg = new GridGeometry(
                 new GridExtent(null, new long[] {x, y}, new long[] {x+width, y+height}, false),
-                new Envelope2D(HardCodedCRS.WGS84, 20, 15, 60, 62));
+                new Envelope2D(HardCodedCRS.WGS84, 20, 15, 60, 62), GridOrientation.HOMOTHETIC);
         return new GridCoverage2D(gg, null, image);
     }
 
@@ -440,7 +440,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
     @Test
     public void testSubGeographicArea() throws TransformException {
         final GridCoverage2D source = createCoverage2D();             // Envelope2D(20, 15,
60, 62)
-        GridGeometry gg = new GridGeometry(null, new Envelope2D(HardCodedCRS.WGS84, 18, 20,
17, 31));
+        GridGeometry gg = new GridGeometry(null, new Envelope2D(HardCodedCRS.WGS84, 18, 20,
17, 31), null);
         final GridCoverage target = resample(source, gg);
         final GridExtent sourceExtent = source.getGridGeometry().getExtent();
         final GridExtent targetExtent = target.getGridGeometry().getExtent();
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
index b780694..a45aec1 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
@@ -37,6 +37,7 @@ import org.apache.sis.internal.geotiff.Resources;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridOrientation;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.math.Vector;
 
@@ -334,7 +335,7 @@ final class GridGeometryBuilder {
                 envelope = new GeneralEnvelope(crs);
                 envelope.setToNaN();
             }
-            gridGeometry = new GridGeometry(extent, envelope);
+            gridGeometry = new GridGeometry(extent, envelope, GridOrientation.HOMOTHETIC);
             canNotCreate(e);
             /*
              * Note: we catch TransformExceptions because they may be caused by erroneous
data in the GeoTIFF file,
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index aab1e7f..5456d32 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -1254,7 +1254,7 @@ final class ImageFileDirectory extends AbstractGridResource {
             }
             return gridGeometry;
         } else {
-            return new GridGeometry(new GridExtent(imageWidth, imageHeight), null);
+            return new GridGeometry(new GridExtent(imageWidth, imageHeight), null, null);
         }
     }
 


Mime
View raw message