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: Move GridCoverage2D to public API and revisit method contract / implementation.
Date Fri, 20 Dec 2019 19:38:13 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 262e966  Move GridCoverage2D to public API and revisit method contract / implementation.
262e966 is described below

commit 262e96653442167e420681e5b2db0431f472aeca
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Dec 20 20:23:54 2019 +0100

    Move GridCoverage2D to public API and revisit method contract / implementation.
---
 .../sis/coverage/grid/DisjointExtentException.java |  17 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   | 517 +++++++++++++++++++++
 .../org/apache/sis/coverage/grid/GridExtent.java   |  61 ++-
 .../grid/IllegalGridGeometryException.java         |  13 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    |   5 +-
 .../org/apache/sis/coverage/grid/package-info.java |   2 +-
 .../java/org/apache/sis/image/LinearIterator.java  |   2 +-
 .../java/org/apache/sis/image/PixelIterator.java   |  28 +-
 .../internal/coverage/BufferedGridCoverage.java    |  26 +-
 .../internal/coverage/ConvertedGridCoverage.java   |  16 +-
 .../sis/internal/coverage/GridCoverage2D.java      | 258 ----------
 .../sis/internal/coverage/ImageUtilities.java      |  14 +
 .../apache/sis/internal/coverage/package-info.java |   2 +-
 .../org/apache/sis/internal/feature/Resources.java |  19 +-
 .../sis/internal/feature/Resources.properties      |   3 +
 .../sis/internal/feature/Resources_fr.properties   |   3 +
 .../apache/sis/internal/feature/package-info.java  |   2 +-
 .../internal/image/TranslatedRenderedImage.java    |  20 +-
 .../grid}/GridCoverage2DTest.java                  |  20 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |  10 +-
 20 files changed, 675 insertions(+), 363 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java
index cefd306..53719d0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DisjointExtentException.java
@@ -24,7 +24,8 @@ import org.apache.sis.internal.feature.Resources;
  * does not intersect anymore the {@link GridExtent} of the {@link GridGeometry}.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -60,7 +61,7 @@ public class DisjointExtentException extends IllegalGridGeometryException {
     }
 
     /**
-     * Creates an exception with an error message built from the given argument.
+     * Creates an exception with an error message built from the given arguments.
      * Current implementation creates the error message immediately, but we may
      * change to deferred creation later if it is a performance issue.
      *
@@ -73,4 +74,16 @@ public class DisjointExtentException extends IllegalGridGeometryException {
     DisjointExtentException(final Object dim, final long min, final long max, final long lower, final long upper) {
         super(Resources.format(Resources.Keys.GridEnvelopeOutsideCoverage_5, new Object[] {dim, min, max, lower, upper}));
     }
+
+    /**
+     * Creates an exception with an error message built from the given extents.
+     *
+     * @param source   extent of the source.
+     * @param request  extent of a slice requested by user.
+     * @param dim      index of the dimension having an invalid value.
+     */
+    DisjointExtentException(final GridExtent source, final GridExtent request, final int dim) {
+        this(source.getAxisIdentification(dim, dim), source.getLow(dim), source.getHigh(dim),
+                request.getLow(dim), request.getHigh(dim));
+    }
 }
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
new file mode 100644
index 0000000..d360e57
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -0,0 +1,517 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+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;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.coverage.CannotEvaluateException;
+import org.opengis.coverage.PointOutsideCoverageException;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.coverage.ImageUtilities;
+import org.apache.sis.internal.coverage.ConvertedGridCoverage;
+import org.apache.sis.internal.image.TranslatedRenderedImage;
+import org.apache.sis.internal.feature.Resources;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Workaround;
+
+
+/**
+ * Basic access to grid data values backed by a two-dimensional {@link RenderedImage}.
+ * While images are two-dimensional, the coverage <em>envelope</em> may have more dimensions.
+ * In other words the rendered image can be a two-dimensional slice in a <var>n</var>-dimensional space.
+ * The only restriction is that the {@linkplain GridGeometry#getExtent() grid extent} has a
+ * {@linkplain GridExtent#getSize(int) size} equals to 1 in all dimensions except two of them.
+ *
+ * <div class="note"><b>Example:</b>
+ * a remote sensing image may be valid only over some time range
+ * (the temporal period of the satellite passing over observed area).
+ * Envelopes for such grid coverage can have three dimensions:
+ * the two usual ones (horizontal extent along <var>x</var> and <var>y</var>),
+ * and a third dimension for start time and end time (temporal extent along <var>t</var>).
+ * This "two-dimensional" grid coverage can have any number of columns along <var>x</var> axis
+ * and any number of rows along <var>y</var> axis, but only one plan along <var>t</var> axis.
+ * This single plan can have a lower bound (the start time) and an upper bound (the end time).
+ * </div>
+ *
+ * <h2>Image size and location</h2>
+ * The {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height}
+ * must be equal to the {@linkplain GridExtent#getSize(int) grid extent size} in the two dimensions of the slice.
+ * However the image origin ({@linkplain RenderedImage#getMinX() minimal x} and {@linkplain RenderedImage#getMinY() y}
+ * values) does not need to be equal to the {@linkplain GridExtent#getLow(int) grid extent low values};
+ * a translation will be applied as needed.
+ *
+ * <h2>Image bands</h2>
+ * Each band in an image is represented as a {@link SampleDimension}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class GridCoverage2D extends GridCoverage {
+    /**
+     * Minimal number of dimension for this coverage.
+     */
+    private static final int MIN_DIMENSION = 2;
+
+    /**
+     * The sample values stored as a {@code RenderedImage}.
+     */
+    private final RenderedImage data;
+
+    /**
+     * Offsets to apply for converting grid coverage coordinates to image pixel coordinates.
+     * This is {@link RenderedImage#getMinX()} − <code>{@linkplain GridExtent#getLow(int)
+     * GridExtent.getLow}({@linkplain #xDimension})</code> for the <var>x</var> offset
+     * and a similar formula for the <var>y</var> offset.
+     */
+    private final long gridToImageX, gridToImageY;
+
+    /**
+     * Indices of extent dimensions corresponding to image <var>x</var> and <var>y</var> coordinates.
+     * Typical values are 0 for {@code xDimension} and 1 for {@code yDimension}, but different values
+     * are allowed.
+     */
+    private final int xDimension, yDimension;
+
+    /**
+     * The two-dimensional components of the coordinate reference system and "grid to CRS" transform.
+     * This is derived from {@link #getGridGeometry()} when first needed, retaining only the components
+     * at dimension indices {@link #xDimension} and {@link #yDimension}.
+     *
+     * @see #getGridGeometry2D()
+     */
+    private transient GridGeometry gridGeometry2D;
+
+    /**
+     * Result of the call to {@link #forConvertedValues(boolean)} with a boolean value opposite to
+     * {@link #isConverted}. This coverage is determined when first needed and may be {@code this}.
+     *
+     * @see #forConvertedValues(boolean)
+     */
+    private transient GridCoverage converse;
+
+    /**
+     * Whether all sample dimensions are already representing converted values.
+     * This field has no meaning if {@link #converse} is null.
+     *
+     * @see #forConvertedValues(boolean)
+     */
+    private transient boolean isConverted;
+
+    /**
+     * Constructs a grid coverage using the specified domain, range and data. If the given domain does not
+     * have an extent, then a default {@link GridExtent} will be computed from given image. Otherwise the
+     * {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height}
+     * must be equal to the {@linkplain GridExtent#getSize(int) grid extent size} in the two dimensions of
+     * the slice.
+     *
+     * <p>The image origin ({@linkplain RenderedImage#getMinX() minimal x} and {@linkplain RenderedImage#getMinY() y}
+     * values) can be anywhere; it does not need to be the same as the {@linkplain GridExtent#getLow(int) grid extent
+     * low values}. Translations will be applied automatically when needed.</p>
+     *
+     * <p>This constructor throws an {@link IllegalGridGeometryException} if one
+     * of the following errors is detected in the {@code domain} argument:</p>
+     * <ul>
+     *   <li>The given domain has less than two dimensions.</li>
+     *   <li>The given domain has more than two dimensions having an
+     *       {@linkplain GridExtent#getSize(int) extent size} greater than 1.</li>
+     *   <li>The extent size along <var>x</var> and <var>y</var> axes is not equal to the image width and height.</li>
+     * </ul>
+     *
+     * @param  domain  the grid extent (may be absent), CRS and conversion from cell indices.
+     *                 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 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.
+     */
+    public GridCoverage2D(GridGeometry domain, final Collection<? extends SampleDimension> range, final RenderedImage data) {
+        super(domain = addExtentIfAbsent(domain, data), defaultIfAbsent(range,  data));
+        this.data = data;           // Non-null verified by addExtentIfAbsent(…, data).
+        /*
+         * Find indices of the two dimensions of the slice. Those dimensions are usually 0 for x and 1 for y,
+         * but not necessarily. A two dimensional CRS will be extracted for those dimensions later if needed.
+         */
+        final GridExtent extent = domain.getExtent();
+        final int[] imageAxes;
+        try {
+            imageAxes = extent.getSubspaceDimensions(MIN_DIMENSION);
+        } catch (CannotEvaluateException e) {
+            throw new IllegalGridGeometryException(e.getMessage(), e);
+        }
+        xDimension   = imageAxes[0];
+        yDimension   = imageAxes[1];
+        gridToImageX = Math.subtractExact(data.getMinX(), extent.getLow(xDimension));
+        gridToImageY = Math.subtractExact(data.getMinY(), extent.getLow(yDimension));
+        /*
+         * Verifiy that the domain is consistent with image size.
+         * We do not verify image location; it can be anywhere.
+         */
+        for (int i=0; i<MIN_DIMENSION; i++) {
+            final int imageSize = (i == 0) ? data.getWidth() : data.getHeight();
+            final long gridSize = extent.getSize(imageAxes[i]);
+            if (imageSize != gridSize) {
+                throw new IllegalGridGeometryException(Resources.format(Resources.Keys.MismatchedImageSize_3, i, imageSize, gridSize));
+            }
+        }
+        verifyBandCount(range, data);
+    }
+
+    /**
+     * If the given domain does not have a {@link GridExtent}, creates a new grid geometry
+     * with an extent computed from the given image. The new grid will start at the same
+     * location than the image and will have the same size.
+     *
+     * <p>This static method is a workaround for RFE #4093999
+     * ("Relax constraint on placement of this()/super() call in constructors").</p>
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static GridGeometry addExtentIfAbsent(GridGeometry domain, final RenderedImage data) {
+        ArgumentChecks.ensureNonNull("data", data);
+        if (domain == null) {
+            GridExtent extent = new GridExtent(data.getMinX(), data.getMinY(), data.getWidth(), data.getHeight());
+            domain = new GridGeometry(extent, PixelInCell.CELL_CENTER, null, null);
+        } else if (!domain.isDefined(GridGeometry.EXTENT)) {
+            final int dimension = domain.getDimension();
+            if (dimension >= MIN_DIMENSION) {
+                CoordinateReferenceSystem crs = null;
+                if (domain.isDefined(GridGeometry.CRS)) {
+                    crs = domain.getCoordinateReferenceSystem();
+                }
+                final GridExtent extent = createExtent(dimension, data, crs);
+                try {
+                    domain = new GridGeometry(domain, extent, null);
+                } catch (TransformException e) {
+                    throw new IllegalGridGeometryException(e);                  // Should never happen.
+                }
+            }
+        }
+        return domain;
+    }
+
+    /**
+     * 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 often expected in 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>
+     *
+     * @todo Not yet public. We should provide an argument controlling whether to flip Y axis.
+     *
+     * @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)
+     */
+    GridCoverage2D(final Envelope domain, final Collection<? extends SampleDimension> range, final RenderedImage data) {
+        super(createGridGeometry(data, domain), defaultIfAbsent(range, data));
+        this.data = data;   // Non-null verified by createGridGeometry(…, data).
+        xDimension   = 0;
+        yDimension   = 1;
+        gridToImageX = 0;
+        gridToImageY = 0;
+        verifyBandCount(range, data);
+    }
+
+    /**
+     * 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 = MIN_DIMENSION;
+        if (envelope != null) {
+            dimension = envelope.getDimension();
+            if (dimension < MIN_DIMENSION) {
+                throw new IllegalGridGeometryException(Resources.format(
+                        Resources.Keys.GridEnvelopeMustBeNDimensional_1, MIN_DIMENSION));
+            }
+            crs = envelope.getCoordinateReferenceSystem();
+        }
+        return new GridGeometry(createExtent(dimension, data, crs), envelope);
+    }
+
+    /**
+     * Creates a grid extent with the low and high coordinates of the given image.
+     * The coordinate reference system is used for extracting grid axis names, in particular
+     * the {@link DimensionNameType#VERTICAL} and {@link DimensionNameType#TIME} dimensions.
+     * The {@link DimensionNameType#COLUMN} and {@link DimensionNameType#ROW} dimensions can
+     * not be inferred from CRS analysis; they are added from knowledge that we have an image.
+     *
+     * @param  dimension  number of dimensions.
+     * @param  data       the image for which to create a grid extent.
+     * @param  crs        coordinate reference system, or {@code null} if none.
+     */
+    private static GridExtent createExtent(final int dimension, final RenderedImage data, final CoordinateReferenceSystem crs) {
+        final long[] low  = new long[dimension];
+        final long[] high = new long[dimension];
+        low [0] = data.getMinX();
+        low [1] = data.getMinY();
+        high[0] = data.getWidth()  + low[0] - 1;        // Inclusive.
+        high[1] = data.getHeight() + low[1] - 1;
+        DimensionNameType[] axisTypes = GridExtent.typeFromAxes(crs, dimension);
+        if (axisTypes == null) {
+            axisTypes = new DimensionNameType[dimension];
+        }
+        if (!ArraysExt.contains(axisTypes, DimensionNameType.COLUMN)) axisTypes[0] = DimensionNameType.COLUMN;
+        if (!ArraysExt.contains(axisTypes, DimensionNameType.ROW))    axisTypes[1] = DimensionNameType.ROW;
+        return new GridExtent(axisTypes, low, high, true);
+    }
+
+    /**
+     * If the sample dimensions are null, creates default sample dimensions
+     * with "gray", "red, green, blue" or "cyan, magenta, yellow" names.
+     */
+    private static Collection<? extends SampleDimension> defaultIfAbsent(
+            Collection<? extends SampleDimension> range, final RenderedImage data)
+    {
+        if (range == null) {
+            final short[] names = ImageUtilities.bandNames(data);
+            final SampleDimension[] sd = new SampleDimension[names.length];
+            final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
+            for (int i=0; i<names.length; i++) {
+                final InternationalString name;
+                final short k = names[0];
+                if (k != 0) {
+                    name = Vocabulary.formatInternational(k);
+                } else {
+                    name = Vocabulary.formatInternational(Vocabulary.Keys.Band_1, i+1);
+                }
+                sd[i] = new SampleDimension(factory.createLocalName(null, name), null, Collections.emptyList());
+            }
+            range = Arrays.asList(sd);
+        }
+        return range;
+    }
+
+    /**
+     * Verifies that the number of bands in the image is equal to the number of sample dimensions.
+     * The number of bands is fetched from the sample model, which in theory shall never be null.
+     * However this class has a little bit of tolerance to missing sample model; it may happen
+     * when the image is used only as a matrix storage.
+     */
+    private static void verifyBandCount(final Collection<? extends SampleDimension> range, final RenderedImage data) {
+        if (range != null) {
+            final SampleModel sm = data.getSampleModel();
+            if (sm != null) {
+                final int nb = sm.getNumBands();
+                final int ns = range.size();
+                if (nb != ns) {
+                    throw new IllegalArgumentException(Resources.format(Resources.Keys.MismatchedBandCount_2, nb, ns));
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the two-dimensional part of this grid geometry.
+     * If the {@linkplain #getGridGeometry() complete geometry} is already two-dimensional,
+     * then this method returns the same geometry. Otherwise it returns a geometry for the two first
+     * axes having a {@linkplain GridExtent#getSize(int) size} greater than 1 in the grid envelope.
+     * Note that those axes are guaranteed to appear in the same order than in the complete geometry.
+     *
+     * @return the two-dimensional part of the grid geometry.
+     *
+     * @see #getGridGeometry()
+     * @see GridGeometry#reduce(int...)
+     */
+    public synchronized GridGeometry getGridGeometry2D() {
+        if (gridGeometry2D == null) {
+            gridGeometry2D = getGridGeometry().reduce(xDimension, yDimension);
+        }
+        return gridGeometry2D;
+    }
+
+    /**
+     * Returns a grid coverage that contains real values or sample values,
+     * depending if {@code converted} is {@code true} or {@code false} respectively.
+     *
+     * If the given value is {@code true}, then the default implementation returns a grid coverage which produces
+     * {@link RenderedImage} views. Those views convert each sample value on the fly. This is known to be very slow
+     * if an entire raster needs to be processed, but this is temporary until another implementation is provided in
+     * a future SIS release.
+     *
+     * @param  converted  {@code true} for a coverage containing converted values,
+     *                    or {@code false} for a coverage containing packed values.
+     * @return a coverage containing converted or packed values, depending on {@code converted} argument value.
+     */
+    @Override
+    public synchronized GridCoverage forConvertedValues(final boolean converted) {
+        if (converse == null) {
+            isConverted = allConvertedFlagEqual(true);
+            if (isConverted) {
+                if (allConvertedFlagEqual(false)) {
+                    // No conversion in any direction.
+                    converse = this;
+                } else {
+                    // Data are converted and user may want a packed format.
+                    converse = ConvertedGridCoverage.createFromConverted(this);
+                }
+            } else {
+                // Anything that need conversion, even if "is packed" test is also false.
+                converse = ConvertedGridCoverage.createFromPacked(this);
+            }
+        }
+        return (converted == isConverted) ? this : converse;
+    }
+
+    /**
+     * Determines whether an "is converted" or "is packed" test on all sample dimensions returns {@code true}.
+     *
+     * @param  converted  {@coce true} for an "is converted" test, or {@code false} for an "is packed" test.
+     * @return whether all sample dimensions in this coverage pass the specified test.
+     */
+    private boolean allConvertedFlagEqual(final boolean converted) {
+        for (final SampleDimension sd : getSampleDimensions()) {
+            if (sd != sd.forConvertedValues(converted)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns a grid data region as a rendered image. The {@code sliceExtent} argument
+     * specifies the area of interest and may be {@code null} for requesting the whole image.
+     * The coordinates given by {@link RenderedImage#getMinX()} and {@link RenderedImage#getMinY() getMinY()}
+     * will be the image location <em>relative to</em> the location specified in {@code sliceExtent}
+     * {@linkplain GridExtent#getLow(int) low coordinates} (see super-class javadoc for more discussion).
+     * The {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height} will be
+     * the {@code sliceExtent} {@linkplain GridExtent#getSize(int) sizes} if this method can honor exactly the request,
+     * but this method is free to return a smaller or larger image if doing so reduce the amount of data to create or copy.
+     * This implementation returns a view as much as possible, without copying sample values.
+     *
+     * @param  sliceExtent  area of interest, or {@code null} for the whole image.
+     * @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}.
+     * @throws DisjointExtentException if the given extent does not intersect this grid coverage.
+     * @throws CannotEvaluateException if this method can not produce the rendered image for another reason.
+     *
+     * @see BufferedImage#getSubimage(int, int, int, int)
+     */
+    @Override
+    public RenderedImage render(final GridExtent sliceExtent) throws CannotEvaluateException {
+        if (sliceExtent == null) {
+            return data;
+        }
+        final GridExtent extent = getGridGeometry().extent;
+        if (extent != null) {
+            for (int i = Math.min(sliceExtent.getDimension(), extent.getDimension()); --i >= 0;) {
+                if (i != xDimension && i != yDimension) {
+                    if (sliceExtent.getLow(i) < extent.getLow(i) || sliceExtent.getHigh(i) > extent.getHigh(i)) {
+                        throw new DisjointExtentException(extent, sliceExtent, i);
+                    }
+                }
+            }
+        }
+        try {
+            final Rectangle bounds = ImageUtilities.getBounds(data);
+            final long x = Math.addExact(sliceExtent.getLow(xDimension), gridToImageX);
+            final long y = Math.addExact(sliceExtent.getLow(yDimension), gridToImageY);
+            /*
+             * The following code clamp values to 32 bits integers without throwing ArithmeticException
+             * because any value that overflow 32 bits are sure to be outside the RenderedImage bounds.
+             * In such case, clamping changes nothing to the result.
+             */
+            final Rectangle request = bounds.intersection(new Rectangle(
+                    (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, x)),
+                    (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, y)),
+                    (int) Math.min(Integer.MAX_VALUE, sliceExtent.getSize(xDimension)),
+                    (int) Math.min(Integer.MAX_VALUE, sliceExtent.getSize(yDimension))));
+            /*
+             * BufferedImage.getSubimage() returns a new image with upper-left coordinate at (0,0),
+             * which is exactly what this method contract is requesting.
+             */
+            if (data instanceof BufferedImage) {
+                final BufferedImage image = (BufferedImage) data;
+                return image.getSubimage(request.x, request.y, request.width, request.height);
+            }
+            return new TranslatedRenderedImage(data,
+                    Math.toIntExact(Math.subtractExact(bounds.x, x)),
+                    Math.toIntExact(Math.subtractExact(bounds.y, y)));
+        } catch (ArithmeticException e) {
+            throw new CannotEvaluateException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Returns a sequence of double values for a given point in the coverage.
+     * The CRS of the given point may be any coordinate reference system,
+     * or {@code null} for the same CRS than this coverage.
+     * The returned sequence contains a value for each {@linkplain SampleDimension sample dimension}.
+     *
+     * @param  point   the coordinate point where to evaluate.
+     * @param  buffer  an array in which to store values, or {@code null} to create a new array.
+     * @return the {@code buffer} array, or a newly created array if {@code buffer} was null.
+     * @throws PointOutsideCoverageException if the evaluation failed because the input point
+     *         has invalid coordinates.
+     * @throws CannotEvaluateException if the values can not be computed at the specified coordinate
+     *         for an other reason.
+     */
+    @Override
+    public double[] evaluate(final DirectPosition point, double[] buffer) throws CannotEvaluateException {
+        try {
+            final FractionalGridCoordinates gc = toGridCoordinates(point);
+            final int x = Math.toIntExact(gc.getCoordinateValue(xDimension));
+            final int y = Math.toIntExact(gc.getCoordinateValue(yDimension));
+            final int xmin = data.getMinX();
+            final int ymin = data.getMinY();
+            if (x >= xmin && x < xmin + (long) data.getWidth() &&
+                y >= ymin && y < ymin + (long) data.getHeight())
+            {
+                final int tx = Math.floorDiv(x - data.getTileGridXOffset(), data.getTileWidth());
+                final int ty = Math.floorDiv(y - data.getTileGridYOffset(), data.getTileHeight());
+                return data.getTile(tx, ty).getPixel(x, y, buffer);
+            }
+        } catch (ArithmeticException | DisjointExtentException ex) {
+            throw (PointOutsideCoverageException) new PointOutsideCoverageException(ex.getMessage(), point).initCause(ex);
+        } catch (IllegalArgumentException | TransformException ex) {
+            throw new CannotEvaluateException(ex.getMessage(), ex);
+        }
+        throw new PointOutsideCoverageException(null, point);
+    }
+}
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 51529ef..12688c5 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
@@ -241,6 +241,24 @@ public class GridExtent implements GridEnvelope, Serializable {
     }
 
     /**
+     * Creates a new grid extent for an image of the given size and location. This constructor
+     * is for {@link GridCoverage2D} internal usage: it does not check for overflow (arguments
+     * are assumed small enough, which is the case when they are converted from {@code int}s),
+     * and argument meanings differ from conventions in public constructors.
+     *
+     * @param  xmin    column index of the first cell.
+     * @param  ymin    row index of the first cell.
+     * @param  width   number of pixels in each row.
+     * @param  height  number of pixels in each column.
+     */
+    GridExtent(final long xmin, final long ymin, final long width, final long height) {
+        this(width, height);
+        for (int i=coordinates.length; --i >= 0;) {
+            coordinates[i] += ((i & 1) == 0) ? xmin : ymin;
+        }
+    }
+
+    /**
      * Constructs a new grid envelope set to the specified coordinates.
      * The given arrays contain a minimum (inclusive) and maximum value for each dimension of the grid coverage.
      * The lowest valid grid coordinates are often zero, but this is not mandatory.
@@ -295,6 +313,29 @@ public class GridExtent implements GridEnvelope, Serializable {
     }
 
     /**
+     * Infers the axis types from the given coordinate reference system.
+     *
+     * @param  crs        the coordinate reference system, or {@code null}.
+     * @param  dimension  number of name type to infer. Shall not be greater than the CRS dimension.
+     */
+    static DimensionNameType[] typeFromAxes(final CoordinateReferenceSystem crs, final int dimension) {
+        DimensionNameType[] axisTypes = null;
+        if (crs != null) {
+            final CoordinateSystem cs = crs.getCoordinateSystem();
+            for (int i=0; i<dimension; i++) {
+                final DimensionNameType type = AXIS_DIRECTIONS.get(AxisDirections.absolute(cs.getAxis(i).getDirection()));
+                if (type != null) {
+                    if (axisTypes == null) {
+                        axisTypes = new DimensionNameType[dimension];
+                    }
+                    axisTypes[i] = type;
+                }
+            }
+        }
+        return axisTypes;
+    }
+
+    /**
      * Creates a new grid extent by rounding the given envelope to (usually) nearest integers.
      * The envelope coordinates shall be cell indices with lower values inclusive and upper values exclusive.
      * {@link Double#NaN} envelope coordinates will be set to the corresponding {@code enclosing} coordinates
@@ -331,21 +372,7 @@ public class GridExtent implements GridEnvelope, Serializable {
         if (enclosing != null && enclosing.types != null) {
             types = enclosing.types;
         } else {
-            DimensionNameType[] axisTypes = null;
-            final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
-            if (crs != null) {
-                final CoordinateSystem cs = crs.getCoordinateSystem();
-                for (int i=0; i<dimension; i++) {
-                    final DimensionNameType type = AXIS_DIRECTIONS.get(AxisDirections.absolute(cs.getAxis(i).getDirection()));
-                    if (type != null) {
-                        if (axisTypes == null) {
-                            axisTypes = new DimensionNameType[dimension];
-                        }
-                        axisTypes[i] = type;
-                    }
-                }
-            }
-            types = validateAxisTypes(axisTypes);
+            types = validateAxisTypes(typeFromAxes(envelope.getCoordinateReferenceSystem(), dimension));
         }
         /*
          * Now computes the grid extent coordinates.
@@ -693,8 +720,8 @@ public class GridExtent implements GridEnvelope, Serializable {
      * {@linkplain #getSize(int) size} greater than 1, then a {@link SubspaceNotSpecifiedException} is thrown.
      * If there is less than <var>s</var> dimensions having a size greater than 1, then the returned list of
      * dimensions is completed with some dimensions of size 1, starting with the first dimensions in this grid
-     * extent, until there is exactly <var>s</var> dimensions. This this grid extent does not have <var>s</var>
-     * dimensions, then a {@link CannotEvaluateException} is thrown.
+     * extent, until there is exactly <var>s</var> dimensions. If this grid extent does not have at least
+     * <var>s</var> dimensions, then a {@link CannotEvaluateException} is thrown.
      *
      * @param  s  number of dimensions of the sub-space.
      * @return indices of sub-space dimensions, in increasing order in an array of length <var>s</var>.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java
index fb0771d..c4a3ab4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/IllegalGridGeometryException.java
@@ -27,7 +27,7 @@ import org.apache.sis.internal.feature.Resources;
  * linear (or affine) "grid to CRS" transform.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -53,6 +53,17 @@ public class IllegalGridGeometryException extends IllegalArgumentException {
     }
 
     /**
+     * Constructs an exception with the specified cause.
+     *
+     * @param  cause  the cause for this exception.
+     *
+     * @since 1.1
+     */
+    public IllegalGridGeometryException(final Throwable cause) {
+        super(cause);
+    }
+
+    /**
      * Constructs an exception with the specified detail message and cause.
      *
      * @param  message  the detail message.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
index f0ef651..42f0ffa 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -79,7 +79,7 @@ import org.apache.sis.math.Vector;
  * Support for tiled images will be added in a future version.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see GridCoverage#render(GridExtent)
  *
@@ -240,8 +240,7 @@ public class ImageRenderer {
         final long ymax = Math.min(sliceExtent.getHigh(yd), source.getHigh(yd));
         if (xmax < xmin || ymax < ymin) {                                           // max are inclusive.
             final int d = (xmax < xmin) ? xd : yd;
-            throw new DisjointExtentException(source.getAxisIdentification(d, d),
-                    source.getLow(d), source.getHigh(d), sliceExtent.getLow(d), sliceExtent.getHigh(d));
+            throw new DisjointExtentException(source, sliceExtent, d);
         }
         width   = Math.incrementExact(Math.toIntExact(xmax - xmin));
         height  = Math.incrementExact(Math.toIntExact(ymax - ymin));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java
index 597ff30..29fb023 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/package-info.java
@@ -21,7 +21,7 @@
  * In the three-dimensional case, the cells are voxels.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
index 7eb1688..9a7cdb7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
@@ -30,7 +30,7 @@ import org.apache.sis.internal.feature.Resources;
 
 /**
  * Iterator for the {@link SequenceType#LINEAR} traversal order.
- * This iterator behaves as is the while image was a single tile.
+ * This iterator behaves as is the whole image was a single tile.
  * Calls to {@link #next()} move the current position by increasing the following values, in order:
  *
  * <ol>
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index a0be7ec..4235a5d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -22,7 +22,6 @@ import java.nio.Buffer;
 import java.awt.Point;
 import java.awt.Dimension;
 import java.awt.Rectangle;
-import java.awt.image.BufferedImage;
 import java.awt.image.DataBuffer;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
@@ -164,31 +163,8 @@ public abstract class PixelIterator {
         numBands        = data.getSampleModel().getNumBands();
         tileWidth       = data.getTileWidth();
         tileHeight      = data.getTileHeight();
-
-        if (data instanceof BufferedImage) {
-            /**
-             * BufferedImage.getSubImage produces an image which raster has a SampleModelTranslateX
-             * which concatenate the offset requested in getSubImage.
-             * This means each tile raster may have a unique offset and that
-             * we can't compute the real tile pixels bounds until we have read the tile raster.
-             *
-             * This PixelIterator assume each tile has a deterministic position in the image
-             * which allows to fetch the exact tiles when required.
-             *
-             * To compensate the raster offset we cheat on the tileGridOffset in this special case.
-             * We can apply this trick only because we assume BufferedImage is the only implementation
-             * to behave this way and because a BufferedImage only has one raster tile.
-             */
-            final Raster rasterTemplate = data.getTile(data.getMinTileX(), data.getMinTileY());
-            final int innerRasterTranslationX = rasterTemplate.getSampleModelTranslateX() - data.getMinX();
-            final int innerRasterTranslationY = rasterTemplate.getSampleModelTranslateY() - data.getMinY();
-            tileGridXOffset = data.getTileGridXOffset() - innerRasterTranslationX;
-            tileGridYOffset = data.getTileGridYOffset() - innerRasterTranslationY;
-        } else {
-            tileGridXOffset = data.getTileGridXOffset();
-            tileGridYOffset = data.getTileGridYOffset();
-        }
-
+        tileGridXOffset = data.getTileGridXOffset();
+        tileGridYOffset = data.getTileGridYOffset();
         bounds          = intersection(data.getMinX(), data.getMinY(), data.getWidth(), data.getHeight(), subArea, window);
         lowerX          = bounds.x;
         lowerY          = bounds.y;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
index 0fae561..4651f7e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.coverage;
 
+import java.util.Collection;
 import java.awt.image.DataBuffer;
 import java.awt.image.DataBufferByte;
 import java.awt.image.DataBufferDouble;
@@ -25,8 +26,6 @@ import java.awt.image.DataBufferShort;
 import java.awt.image.DataBufferUShort;
 import java.awt.image.RasterFormatException;
 import java.awt.image.RenderedImage;
-import java.awt.image.SampleModel;
-import java.util.Collection;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridExtent;
@@ -44,7 +43,7 @@ import org.opengis.coverage.CannotEvaluateException;
  * Those data can be shown as {@link RenderedImage}.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -125,7 +124,7 @@ public class BufferedGridCoverage extends GridCoverage {
      * Returns a grid coverage that contains real values or sample values, depending if {@code converted} is {@code true}
      * or {@code false} respectively.
      *
-     * If the given value is {@code false}, then the default implementation returns a grid coverage which produces
+     * If the given value is {@code true}, then the default implementation returns a grid coverage which produces
      * {@link RenderedImage} views. Those views convert each sample value on the fly. This is known to be very slow
      * if an entire raster needs to be processed, but this is temporary until another implementation is provided in
      * a future SIS release.
@@ -137,28 +136,11 @@ public class BufferedGridCoverage extends GridCoverage {
         if (converted) {
             synchronized (this) {
                 if (this.converted == null) {
-                    this.converted = convert(this);
+                    this.converted = ConvertedGridCoverage.createFromPacked(this);
                 }
                 return this.converted;
             }
         }
         return this;
     }
-
-    /**
-     * Returns a coverage for converted values. If the given coverage is already converted,
-     * then this method returns the given {@code coverage} unchanged.
-     *
-     * <p><b>WARNING: this is a temporary implementation.</b>
-     * This method uses a special {@link SampleModel} in departure with the contract documented in JDK javadoc.
-     * That sample model does not only define the sample layout (pixel stride, scanline stride, <i>etc.</i>), but
-     * also converts the sample values. This may be an issue for optimized pipelines accessing {@link DataBuffer}
-     * directly. This method may be replaced by another mechanism (creating new tiles) in a future SIS version.</p>
-     *
-     * @param  packed  the coverage containing packed values to convert.
-     * @return the converted coverage. May be {@code coverage}.
-     */
-    public static GridCoverage convert(final GridCoverage packed) {
-        return ConvertedGridCoverage.create(packed);
-    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
index 5fed1e8..d3c8ec8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ConvertedGridCoverage.java
@@ -48,19 +48,20 @@ import org.opengis.referencing.operation.TransformException;
  * directly. This class may be replaced by another mechanism (creating new tiles) in a future SIS version.</p>
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
-final class ConvertedGridCoverage extends GridCoverage {
+public final class ConvertedGridCoverage extends GridCoverage {
     /**
-     * Returns a coverage for converted values. If the given coverage is already converted,
+     * Returns a coverage for converted values computed from a coverage of package values.
+     * If the given coverage is already converted,
      * then this method returns the given {@code coverage} unchanged.
      *
      * @param  packed  the coverage containing packed values to convert.
      * @return the converted coverage. May be {@code coverage}.
      */
-    public static GridCoverage create(final GridCoverage packed) {
+    public static GridCoverage createFromPacked(final GridCoverage packed) {
         final List<SampleDimension> sds = packed.getSampleDimensions();
         final List<SampleDimension> cfs = new ArrayList<>(sds.size());
         for (SampleDimension sd : sds) {
@@ -70,6 +71,13 @@ final class ConvertedGridCoverage extends GridCoverage {
     }
 
     /**
+     * @todo Placeholder for future evolution.
+     */
+    public static GridCoverage createFromConverted(final GridCoverage converted) {
+        throw new UnsupportedOperationException("\"Converted to packed\" not yet implemented.");
+    }
+
+    /**
      * The coverage containing packed values. Sample values will be converted from this coverage.
      */
     private final GridCoverage packed;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java
deleted file mode 100644
index f0bcf3b..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/GridCoverage2D.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.internal.coverage;
-
-import java.util.Collection;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-import org.opengis.util.FactoryException;
-import org.opengis.geometry.DirectPosition;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
-import org.apache.sis.coverage.SampleDimension;
-import org.apache.sis.coverage.grid.FractionalGridCoordinates;
-import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.GridGeometry;
-import org.apache.sis.coverage.grid.DisjointExtentException;
-import org.apache.sis.coverage.grid.IllegalGridGeometryException;
-import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
-import org.apache.sis.internal.image.TranslatedRenderedImage;
-import org.apache.sis.internal.referencing.AxisDirections;
-import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.operation.transform.TransformSeparator;
-import org.apache.sis.util.ArgumentChecks;
-
-
-/**
- * Basic access to grid data values backed by a two-dimensional {@link RenderedImage}.
- * Each band in an image is represented as a {@link SampleDimension}.
- * The rendered image can be a two-dimensional slice in a <var>n</var>-dimensional space
- * (i.e. the {@linkplain GridGeometry#getEnvelope() grid geometry envelope} may have more
- * than two dimensions) provided that the {@linkplain GridExtent grid extent} have a
- * {@linkplain GridExtent#getSize size} equals to 1 in all dimensions except 2.
- *
- * <div class="note"><b>Example:</b>
- * a remote sensing image may be valid only over some time range
- * (the time of satellite pass over the observed area).
- * Envelopes for such grid coverage can have three dimensions:
- * the two usual ones (horizontal extent along <var>x</var> and <var>y</var>),
- * and a third one for start time and end time (time extent along <var>t</var>).
- * The "two-dimensional" grid coverage can have any number of columns along <var>x</var> axis
- * and any number of rows along <var>y</var> axis, but only one plan along <var>t</var> axis.
- * This single plan can have a lower bound (the start time) and an upper bound (the end time).
- * </div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final class GridCoverage2D extends GridCoverage {
-    /**
-     * The sample values stored as a {@code RenderedImage}.
-     */
-    private final RenderedImage data;
-
-    /**
-     * Index of extent dimensions corresponding to image <var>x</var> and <var>y</var> coordinates.
-     * Typical values are 0 for {@code xDimension} and 1 for {@code yDimension}, but different values
-     * are allowed.
-     */
-    private final int xDimension, yDimension;
-
-    /**
-     * The two-dimensional component of the coordinate reference system, or {@code null} if unspecified.
-     */
-    private final CoordinateReferenceSystem crs2D;
-
-    /**
-     * Result of the call to {@link #forConvertedValues(boolean)}, created when first needed.
-     */
-    private transient GridCoverage converted;
-
-    /**
-     * Constructs a grid coverage using the specified domain, range and data.
-     * The given RenderedImage may not start at 0,0, so does the gridExtent of the grid geometry.
-     * Image 0/0 coordinate is expected to match grid extent lower corner.
-     *
-     * @param  domain  the grid extent, CRS and conversion from cell indices to CRS.
-     * @param  range   sample dimensions for each image band.
-     * @param  data    the sample values as a RenderedImage, potentially multi-banded in packed view.
-     */
-    public GridCoverage2D(final GridGeometry domain, final Collection<? extends SampleDimension> range, final RenderedImage data) {
-        super(domain, range);
-        this.data = data;
-        ArgumentChecks.ensureNonNull("image", data);
-        /*
-         * Extract the 2D components of the coordinate reference system.
-         */
-        final GridExtent extent = domain.getExtent();
-        final int[] imageAxes = extent.getSubspaceDimensions(2);
-        xDimension = imageAxes[0];
-        yDimension = imageAxes[1];
-        if (domain.isDefined(GridGeometry.CRS)) {
-            final CoordinateReferenceSystem crs = domain.getCoordinateReferenceSystem();
-            try {
-                crs2D = CRS.reduce(crs, imageAxes);
-            } catch (IllegalArgumentException | FactoryException e) {
-                throw new IllegalGridGeometryException("Can not create a two-dimensional CRS from " + crs.getName(), e);
-            }
-        } else {
-            crs2D = null;
-        }
-        /*
-         * Check that image is coherent with grid geometry.
-         */
-        int  actual;
-        long expected;
-        if ((actual = data.getWidth()) != (expected = extent.getSize(xDimension))) {
-            throw new IllegalArgumentException("Image width " + actual + " does not match grid extent width " + expected);
-        }
-        if ((actual = data.getHeight()) != (expected = extent.getSize(yDimension))) {
-            throw new IllegalArgumentException("Image height " + actual + " does not match grid extent height " + expected);
-        }
-        int n;
-        if ((actual = data.getSampleModel().getNumBands()) != (n = range.size())) {
-            throw new IllegalArgumentException("Image sample model number of bands " + actual + " does not match number of sample dimensions " + n);
-        }
-    }
-
-    /**
-     * Returns the two-dimensional part of this grid coverage CRS.
-     * If the {@linkplain #getCoordinateReferenceSystem complete CRS} is two-dimensional,
-     * then this method returns the same CRS. Otherwise it returns a CRS for the two first axis
-     * having a {@linkplain GridExtent#getSize(int) size} greater than 1 in the grid envelope.
-     * Note that those axis are guaranteed to appear in the same order than in the complete CRS.
-     *
-     * @return the two-dimensional part of the grid coverage CRS.
-     * @throws IncompleteGridGeometryException if the grid geometry does not contain a CRS.
-     *
-     * @see #getCoordinateReferenceSystem()
-     */
-    public CoordinateReferenceSystem getCoordinateReferenceSystem2D() {
-        if (crs2D != null) {
-            return crs2D;
-        }
-        throw new IncompleteGridGeometryException(Resources.format(Resources.Keys.UnspecifiedCRS));
-    }
-
-    /**
-     * Returns the grid to CRS 2D transform in pixel center.
-     *
-     * @return MathTransform grid to CRS 2D transform
-     * @throws FactoryException if separating 2d transform fails.
-     */
-    public MathTransform getGridToCrs2D() throws FactoryException {
-        TransformSeparator sep = new TransformSeparator(getGridGeometry().getGridToCRS(PixelInCell.CELL_CENTER));
-        int idx = AxisDirections.indexOfColinear(getCoordinateReferenceSystem().getCoordinateSystem(), crs2D.getCoordinateSystem());
-        sep.addSourceDimensionRange(idx, idx+2);
-        return sep.separate();
-    }
-
-    /**
-     * Returns a grid coverage that contains real values or sample values, depending if {@code converted} is {@code true}
-     * or {@code false} respectively.
-     *
-     * If the given value is {@code false}, then the default implementation returns a grid coverage which produces
-     * {@link RenderedImage} views. Those views convert each sample value on the fly. This is known to be very slow
-     * if an entire raster needs to be processed, but this is temporary until another implementation is provided in
-     * a future SIS release.
-     *
-     * @return a coverage containing converted or packed values, depending on {@code converted} argument value.
-     */
-    @Override
-    public GridCoverage forConvertedValues(final boolean converted) {
-        if (converted) {
-            synchronized (this) {
-                if (this.converted == null) {
-                    this.converted = BufferedGridCoverage.convert(this);
-                }
-                return this.converted;
-            }
-        }
-        return this;
-    }
-
-    /**
-     * Returns a two-dimensional slice of grid data as a rendered image.
-     * This method may return a view or a copy.
-     *
-     * @return the grid slice as a rendered image.
-     */
-    @Override
-    public RenderedImage render(final GridExtent sliceExtent) throws CannotEvaluateException {
-        if (sliceExtent == null || sliceExtent.equals(getGridGeometry().getExtent())) {
-            return data;
-        } else {
-            final int subX = Math.toIntExact(sliceExtent.getLow(xDimension));
-            final int subY = Math.toIntExact(sliceExtent.getLow(yDimension));
-            final int subWidth = Math.toIntExact(Math.round(sliceExtent.getSize(xDimension)));
-            final int subHeight = Math.toIntExact(Math.round(sliceExtent.getSize(yDimension)));
-
-            if (data instanceof BufferedImage) {
-                final BufferedImage bi = (BufferedImage) data;
-                return bi.getSubimage(subX, subY, subWidth, subHeight);
-            } else {
-                return new TranslatedRenderedImage(data, subX, subY);
-            }
-        }
-    }
-
-    /**
-     * Returns a sequence of double values for a given point in the coverage.
-     * The CRS of the given point may be any coordinate reference system,
-     * or {@code null} for the same CRS than this coverage.
-     * The returned sequence contains a value for each {@linkplain SampleDimension sample dimension}.
-     *
-     * @param  point   the coordinate point where to evaluate.
-     * @param  buffer  an array in which to store values, or {@code null} to create a new array.
-     * @return the {@code buffer} array, or a newly created array if {@code buffer} was null.
-     * @throws PointOutsideCoverageException if the evaluation failed because the input point
-     *         has invalid coordinates.
-     * @throws CannotEvaluateException if the values can not be computed at the specified coordinate
-     *         for an other reason.
-     */
-    @Override
-    public double[] evaluate(final DirectPosition point, double[] buffer) throws CannotEvaluateException {
-        try {
-            final FractionalGridCoordinates gc = toGridCoordinates(point);
-            final int x = Math.toIntExact(gc.getCoordinateValue(xDimension));
-            final int y = Math.toIntExact(gc.getCoordinateValue(yDimension));
-            final int xmin = data.getMinX();
-            final int ymin = data.getMinY();
-            if (x >= xmin && x < xmin + (long) data.getWidth() &&
-                y >= ymin && y < ymin + (long) data.getHeight())
-            {
-                final int tx = Math.floorDiv(x - data.getTileGridXOffset(), data.getTileWidth());
-                final int ty = Math.floorDiv(y - data.getTileGridYOffset(), data.getTileHeight());
-                return data.getTile(tx, ty).getPixel(x, y, buffer);
-            }
-        } catch (ArithmeticException | DisjointExtentException ex) {
-            throw (PointOutsideCoverageException) new PointOutsideCoverageException(ex.getMessage(), point).initCause(ex);
-        } catch (IllegalArgumentException | TransformException ex) {
-            throw new CannotEvaluateException(ex.getMessage(), ex);
-        }
-        throw new PointOutsideCoverageException(null, point);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ImageUtilities.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ImageUtilities.java
index 73fa6aa..2dcab5a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ImageUtilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/ImageUtilities.java
@@ -17,10 +17,12 @@
 package org.apache.sis.internal.coverage;
 
 import java.util.Arrays;
+import java.awt.Rectangle;
 import java.awt.color.ColorSpace;
 import java.awt.image.ColorModel;
 import java.awt.image.PackedColorModel;
 import java.awt.image.RenderedImage;
+import java.awt.image.Raster;
 import java.awt.image.SampleModel;
 import java.awt.image.SinglePixelPackedSampleModel;
 import org.apache.sis.internal.system.Modules;
@@ -44,6 +46,18 @@ public final class ImageUtilities {
     }
 
     /**
+     * Returns the bounds of the given image as a new rectangle.
+     *
+     * @param  image  the image for which to get the bounds.
+     * @return the bounds of the given image.
+     *
+     * @see Raster#getBounds()
+     */
+    public static Rectangle getBounds(final RenderedImage image) {
+        return new Rectangle(image.getMinX(), image.getMinY(), image.getWidth(), image.getHeight());
+    }
+
+    /**
      * Returns names of bands based on inspection of the color model.
      * The bands are identified by {@link Vocabulary.Keys} values for
      * red, green, blue, cyan, magenta, yellow, black, gray, <i>etc</i>.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/package-info.java
index b390cb7..68fe5d0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/package-info.java
@@ -24,7 +24,7 @@
  * may change in incompatible ways in any future version without notice.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
index 3e23ce6..195583c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
@@ -43,7 +43,7 @@ public final class Resources extends IndexedResourceBundle {
      * pools of compiled classes.
      *
      * @author  Martin Desruisseaux (IRD, Geomatys)
-     * @since   0.8
+     * @since   1.1
      * @module
      */
     @Generated("org.apache.sis.util.resources.IndexedResourceCompiler")
@@ -70,6 +70,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotAssignCharacteristics_1 = 2;
 
         /**
+         * Can not create a two-dimensional reference system from the “{0}” system.
+         */
+        public static final short CanNotCreateTwoDimensionalCRS_1 = 60;
+
+        /**
          * Can not enumerate values in the {0} range.
          */
         public static final short CanNotEnumerateValuesInRange_1 = 23;
@@ -199,6 +204,12 @@ public final class Resources extends IndexedResourceBundle {
         public static final short IterationNotStarted = 37;
 
         /**
+         * Image number of bands {0,number} does not match the number of sample dimensions
+         * ({1,number}).
+         */
+        public static final short MismatchedBandCount_2 = 61;
+
+        /**
          * The bands have different number of sample values.
          */
         public static final short MismatchedBandSize = 38;
@@ -214,6 +225,12 @@ public final class Resources extends IndexedResourceBundle {
         public static final short MismatchedImageLocation = 40;
 
         /**
+         * Image {0,choice,0#width|1#height} ({1,number} pixels) does not match the grid extent size
+         * ({2,number} cells).
+         */
+        public static final short MismatchedImageSize_3 = 62;
+
+        /**
          * Mismatched type for “{0}” property.
          */
         public static final short MismatchedPropertyType_1 = 12;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
index f1153af..e950146 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
@@ -21,6 +21,7 @@
 #
 AbstractFeatureType_1             = Feature type \u2018{0}\u2019 is abstract.
 CanNotAssignCharacteristics_1     = Can not assign characteristics to the \u201c{0}\u201d property.
+CanNotCreateTwoDimensionalCRS_1   = Can not create a two-dimensional reference system from the \u201c{0}\u201d system.
 CanNotEnumerateValuesInRange_1    = Can not enumerate values in the {0} range.
 CanNotInstantiateProperty_1       = Property \u201c{0}\u201d is not a type that can be instantiated.
 CanNotMapToGridDimensions         = Some envelope dimensions can not be mapped to grid dimensions.
@@ -46,9 +47,11 @@ IncompatibleTile_2                = The ({0}, {1}) tile has an unexpected size,
 InvalidExpression_2               = Invalid or unsupported \u201c{1}\u201d expression at index {0}.
 IterationIsFinished               = Iteration is finished.
 IterationNotStarted               = Iteration did not started.
+MismatchedBandCount_2             = Image number of bands {0,number} does not match the number of sample dimensions ({1,number}).
 MismatchedBandSize                = The bands have different number of sample values.
 MismatchedDataType                = The bands store sample values using different data types.
 MismatchedImageLocation           = The two images have different size or pixel coordinates.
+MismatchedImageSize_3             = Image {0,choice,0#width|1#height} ({1,number} pixels) does not match the grid extent size ({2,number} cells).
 MismatchedPropertyType_1          = Mismatched type for \u201c{0}\u201d property.
 MismatchedSampleModel             = The two images use different sample models.
 MismatchedTileGrid                = The two images have different tile grid.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
index a0f13a6..1b0618b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
@@ -26,6 +26,7 @@
 #
 AbstractFeatureType_1             = Le type d\u2019entit\u00e9 \u2018{0}\u2019 est abstrait.
 CanNotAssignCharacteristics_1     = Ne peut pas assigner des caract\u00e9ristiques \u00e0 la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb.
+CanNotCreateTwoDimensionalCRS_1   = Ne peut pas cr\u00e9er un syst\u00e8me de r\u00e9f\u00e9rence bidimensionnel \u00e0 partir du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb.
 CanNotEnumerateValuesInRange_1    = Ne peut pas \u00e9num\u00e9rer les valeurs dans la plage {0}.
 CanNotInstantiateProperty_1       = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas d\u2019un type qui peut \u00eatre instanci\u00e9.
 CanNotMapToGridDimensions         = Certaines dimensions de l\u2019enveloppe ne correspondent pas \u00e0 des dimensions de la grille.
@@ -51,9 +52,11 @@ IncompatibleTile_2                = La tuile ({0}, {1}) a une taille, un nombre
 InvalidExpression_2               = Expression \u00ab\u202f{1}\u202f\u00bb invalide ou non-support\u00e9e \u00e0 l\u2019index {0}.
 IterationIsFinished               = L\u2019it\u00e9ration est termin\u00e9e.
 IterationNotStarted               = L\u2019it\u00e9ration n\u2019a pas commenc\u00e9e.
+MismatchedBandCount_2             = Le nombre de bandes de l\u2019image ({0,number}) ne correspond pas au nombre de dimensions d\u2019\u00e9chantillonnage ({1,number}).
 MismatchedBandSize                = Les bandes ont un nombre diff\u00e9rent de valeurs.
 MismatchedDataType                = Les bandes stockent leurs valeurs en utilisant des types de donn\u00e9es diff\u00e9rents.
 MismatchedImageLocation           = Les deux images ont une taille ou des coordonn\u00e9es pixels diff\u00e9rentes.
+MismatchedImageSize_3             = La {0,choice,0#largeur|1#hauteur} de l\u2019image ({1,number} pixels) ne correspond pas \u00e0 l\u2019\u00e9tendue de la grille ({2,number} cellules).
 MismatchedPropertyType_1          = Le type de la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb ne correspond pas.
 MismatchedSampleModel             = Les deux images disposent les pixels diff\u00e9remment.
 MismatchedTileGrid                = Les deux images utilisent des grilles de tuiles diff\u00e9rentes.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
index 9db59f4..dfd97c4 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
@@ -27,7 +27,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.7
  * @module
  */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/image/TranslatedRenderedImage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/image/TranslatedRenderedImage.java
index 2d08aec..dd21d59 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/image/TranslatedRenderedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/image/TranslatedRenderedImage.java
@@ -22,12 +22,13 @@ import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
 import java.util.Vector;
 
+
 /**
  * Translated RenderedImage implementation.
  *
- * @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since   2.0
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.1
+ * @since   1.1
  * @module
  */
 public final class TranslatedRenderedImage extends AbstractRenderedImage {
@@ -81,12 +82,12 @@ public final class TranslatedRenderedImage extends AbstractRenderedImage {
 
     @Override
     public int getMinX() {
-        return image.getMinX() + offsetX;
+        return Math.addExact(image.getMinX(), offsetX);
     }
 
     @Override
     public int getMinY() {
-        return image.getMinY() + offsetY;
+        return Math.addExact(image.getMinY(), offsetY);
     }
 
     @Override
@@ -121,18 +122,19 @@ public final class TranslatedRenderedImage extends AbstractRenderedImage {
 
     @Override
     public int getTileGridXOffset() {
-        return image.getTileGridXOffset() + offsetX;
+        return Math.addExact(image.getTileGridXOffset(), offsetX);
     }
 
     @Override
     public int getTileGridYOffset() {
-        return image.getTileGridYOffset() + offsetY;
+        return Math.addExact(image.getTileGridYOffset(), offsetY);
     }
 
     @Override
     public Raster getTile(int tileX, int tileY) {
         final Raster tile = image.getTile(tileX, tileY);
-        return tile.createTranslatedChild(offsetX, offsetY);
+        return tile.createTranslatedChild(
+                Math.addExact(tile.getMinX(), offsetX),
+                Math.addExact(tile.getMinY(), offsetY));
     }
-
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
similarity index 95%
rename from core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java
rename to core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 0bc953b..cefd2cf 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/coverage/GridCoverage2DTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.coverage;
+package org.apache.sis.coverage.grid;
 
 import java.awt.Point;
 import java.awt.Transparency;
@@ -27,11 +27,13 @@ import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
 import java.util.Arrays;
 import java.util.Hashtable;
+import org.opengis.util.FactoryException;
+import org.opengis.coverage.PointOutsideCoverageException;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.coverage.SampleDimension;
-import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.internal.coverage.ColorModelFactory;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
@@ -39,18 +41,14 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.test.TestCase;
 import org.junit.Assert;
 import org.junit.Test;
-import org.opengis.coverage.PointOutsideCoverageException;
-import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.referencing.operation.MathTransform1D;
-import org.opengis.util.FactoryException;
 
 
 /**
  * Tests the {@link GridCoverage2D} implementation.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 2.0
- * @since   2.0
+ * @version 1.1
+ * @since   1.1
  * @module
  */
 public class GridCoverage2DTest extends TestCase {
@@ -77,7 +75,7 @@ public class GridCoverage2DTest extends TestCase {
         final ColorSpace colors = ColorModelFactory.createColorSpace(1, 0, -10, 10);
         final ColorModel cm = new ComponentColorModel(colors, false, false, Transparency.OPAQUE, DataBuffer.TYPE_INT);
         BufferedImage image = new BufferedImage(cm, raster, false, new Hashtable<>());
-        GridCoverage   coverage = new GridCoverage2D(grid, Arrays.asList(sd), image);
+        GridCoverage coverage = new GridCoverage2D(grid, Arrays.asList(sd), image);
         raster.setSample(0, 0, 0,   0);
         raster.setSample(1, 0, 0,   5);
         raster.setSample(0, 1, 0,  -5);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index edd4256..ec05519 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -76,19 +76,19 @@ import org.junit.runners.Suite;
     // Rasters
     org.apache.sis.image.DefaultIteratorTest.class,
     org.apache.sis.image.LinearIteratorTest.class,
+    org.apache.sis.coverage.CategoryTest.class,
+    org.apache.sis.coverage.CategoryListTest.class,
+    org.apache.sis.coverage.SampleDimensionTest.class,
+    org.apache.sis.coverage.SampleRangeFormatTest.class,
     org.apache.sis.coverage.grid.PixelTranslationTest.class,
     org.apache.sis.coverage.grid.GridExtentTest.class,
     org.apache.sis.coverage.grid.GridGeometryTest.class,
     org.apache.sis.coverage.grid.GridDerivationTest.class,
     org.apache.sis.coverage.grid.FractionalGridCoordinates.class,
-    org.apache.sis.coverage.CategoryTest.class,
-    org.apache.sis.coverage.CategoryListTest.class,
-    org.apache.sis.coverage.SampleDimensionTest.class,
-    org.apache.sis.coverage.SampleRangeFormatTest.class,
+    org.apache.sis.coverage.grid.GridCoverage2DTest.class,
     org.apache.sis.internal.coverage.ImageUtilitiesTest.class,
     org.apache.sis.internal.coverage.ScaledColorSpaceTest.class,
     org.apache.sis.internal.coverage.BufferedGridCoverageTest.class,
-    org.apache.sis.internal.coverage.GridCoverage2DTest.class,
     org.apache.sis.internal.image.TranslatedRenderedImageTest.class
 })
 public final strictfp class FeatureTestSuite extends TestSuite {


Mime
View raw message