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: Prepare for implementation of GridCoverage.render(...) in netCDF resources. Refactor GridExtent private constructor as a subsample method. Consolidation of dimension checks (MismatchedDimensionException). Fix a typo in resource method documentations.
Date Mon, 31 Dec 2018 22:31:22 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 1a6076c  Prepare for implementation of GridCoverage.render(...) in netCDF resources. Refactor GridExtent private constructor as a subsample method. Consolidation of dimension checks (MismatchedDimensionException). Fix a typo in resource method documentations.
1a6076c is described below

commit 1a6076c39242824e2a6b4781aac62110981627c1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Dec 31 23:28:47 2018 +0100

    Prepare for implementation of GridCoverage.render(...) in netCDF resources.
    Refactor GridExtent private constructor as a subsample method.
    Consolidation of dimension checks (MismatchedDimensionException).
    Fix a typo in resource method documentations.
---
 .../org/apache/sis/internal/gui/Resources.java     |   8 +-
 .../org/apache/sis/internal/feature/Resources.java |  10 +-
 .../apache/sis/internal/metadata/Resources.java    |   4 +-
 .../java/org/apache/sis/coverage/CategoryList.java |   4 +-
 .../org/apache/sis/coverage/grid/GridChange.java   |  32 +++-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 191 ++++++++++++++-------
 .../org/apache/sis/coverage/grid/GridGeometry.java |  48 +++---
 .../sis/coverage/grid/SubgridCalculator.java       |  99 ++++++++---
 .../java/org/apache/sis/image/DefaultIterator.java |   2 +-
 .../sis/internal/raster/ColorModelFactory.java     |  20 ++-
 .../org/apache/sis/internal/raster/Resources.java  |  44 ++++-
 .../sis/internal/raster/Resources.properties       |   4 +-
 .../sis/internal/raster/Resources_fr.properties    |   4 +-
 .../apache/sis/coverage/grid/GridExtentTest.java   |   6 +-
 .../apache/sis/internal/gazetteer/Resources.java   |   8 +-
 .../internal/referencing/ReferencingUtilities.java |   1 +
 .../apache/sis/internal/referencing/Resources.java |  10 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |   3 +-
 .../sis/referencing/datum/DatumShiftGrid.java      |   6 +-
 .../operation/DefaultConcatenatedOperation.java    |   3 +-
 .../operation/builder/LinearTransformBuilder.java  |  13 +-
 .../java/org/apache/sis/util/ArgumentChecks.java   |  32 +++-
 .../java/org/apache/sis/util/resources/Errors.java |  10 +-
 .../org/apache/sis/util/resources/Messages.java    |  10 +-
 .../org/apache/sis/internal/netcdf/Resources.java  |  10 +-
 .../java/org/apache/sis/storage/netcdf/Image.java  |   3 +-
 .../apache/sis/internal/sql/feature/Resources.java |   6 +-
 .../org/apache/sis/internal/storage/Resources.java |   8 +-
 28 files changed, 406 insertions(+), 193 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index 152237a..42b165c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -116,8 +116,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -131,7 +131,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -148,7 +148,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
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 faba416..29daf95 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
@@ -233,8 +233,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -248,7 +248,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -265,7 +265,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
@@ -284,7 +284,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java
index 63b8335..0a3d1b9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java
@@ -124,8 +124,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
index d85874f..13d7818 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
@@ -167,9 +167,9 @@ final class CategoryList extends AbstractList<Category> implements MathTransform
             if (i != 0) {
                 final Category previous = categories[i-1];
                 if (Category.compare(category.minimum, previous.maximum) <= 0) {
-                    throw new IllegalArgumentException(Resources.format(Resources.Keys.CategoryRangeOverlap_4, new Object[] {
+                    throw new IllegalArgumentException(Resources.format(Resources.Keys.CategoryRangeOverlap_4,
                                 previous.name, previous.getRangeLabel(),
-                                category.name, category.getRangeLabel()}));
+                                category.name, category.getRangeLabel()));
                 }
             }
             final NumberRange<?> extent = category.range;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
index f044f27..70407d9 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
@@ -344,14 +344,14 @@ public class GridChange implements Serializable {
         double[] factors = null;
         Matrix toGiven = null;
         if (strides != null) {
-            // Validity of the strides values will be verified by GridExtent constructor invoked below.
+            // Validity of the strides values will be verified by GridExtent.subsampling(…) invoked below.
             final GridExtent unscaled = extent;
             final int dimension = extent.getDimension();
             for (int i = Math.min(dimension, strides.length); --i >= 0;) {
                 final int s = strides[i];
                 if (s != 1) {
                     if (factors == null) {
-                        extent = new GridExtent(extent, strides);
+                        extent = extent.subsample(strides);
                         factors = new double[dimension];
                         Arrays.fill(factors, 1);
                         if (!extent.startsWithZero()) {
@@ -361,22 +361,44 @@ public class GridChange implements Serializable {
                     final double sd = s;
                     factors[i] = sd;
                     if (toGiven != null) {
-                        final long low = unscaled.getLow(i);
                         toGiven.setElement(i, i, sd);
-                        toGiven.setElement(i, dimension, low - (low / s) * sd);     // (low / s) must be consistent with GridExtent.
+                        toGiven.setElement(i, dimension, unscaled.getLow(i) - extent.getLow(i) * sd);
                     }
                 }
             }
         }
         final MathTransform mt;
         if (factors == null) {
-            if (extent.equals(givenGeometry.extent)) {
+            if (extent == givenGeometry.extent) {
                 return givenGeometry;
             }
             mt = null;
         } else {
             mt = (toGiven != null) ? MathTransforms.linear(toGiven) : MathTransforms.scale(factors);
         }
+        /*
+         * Assuming:
+         *
+         *   • All low coordinates = 0
+         *   • h₁ the high coordinate before sub-sampling
+         *   • h₂ the high coordinates after sub-sampling
+         *   • c  a conversion factor from grid indices to "real world" coordinates
+         *   • s  a sub-sampling ≧ 1
+         *
+         * Then the envelope upper bounds x is:
+         *
+         *   • x = (h₁ + 1) × c
+         *   • x = (h₂ + f) × c⋅s      which implies       h₂ = h₁/s      and       f = 1/s
+         *
+         * If we modify the later equation for integer division instead than real numbers, we have:
+         *
+         *   • x = (h₂ + f) × c⋅s      where        h₂ = floor(h₁/s)      and       f = ((h₁ mod s) + 1)/s
+         *
+         * Because s ≧ 1, then f ≦ 1. But the f value actually used by GridExtent.toCRS(…) is hard-coded to 1
+         * since it assumes that all cells are whole, i.e. it does not take in account that the last cell may
+         * actually be fraction of a cell. Since 1 ≧ f, the computed envelope may be larger. This explains the
+         * need for envelope clipping performed by GridGeometry constructor.
+         */
         return new GridGeometry(givenGeometry, extent, mt);
     }
 
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 28d1190..25fa0f2 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -25,6 +25,7 @@ import java.io.Serializable;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import org.opengis.util.FactoryException;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -49,6 +50,7 @@ import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
 import org.opengis.coverage.grid.GridEnvelope;
+import org.opengis.coverage.PointOutsideCoverageException;
 
 
 /**
@@ -146,14 +148,14 @@ public class GridExtent implements Serializable {
      * @throws IllegalArgumentException if a coordinate value in the low part is
      *         greater than the corresponding coordinate value in the high part.
      */
-    private static void checkCoherence(final long[] coordinates) throws IllegalArgumentException {
+    private void validateCoordinates() throws IllegalArgumentException {
         final int dimension = coordinates.length >>> 1;
         for (int i=0; i<dimension; i++) {
             final long lower = coordinates[i];
             final long upper = coordinates[i + dimension];
             if (lower > upper) {
                 throw new IllegalArgumentException(Resources.format(
-                        Resources.Keys.IllegalGridEnvelope_3, i, lower, upper));
+                        Resources.Keys.IllegalGridEnvelope_3, getAxisIdentification(i,i), lower, upper));
             }
         }
     }
@@ -204,6 +206,8 @@ public class GridExtent implements Serializable {
      *
      * @param dimension  number of dimensions.
      * @param axisTypes  the axis types, or {@code null} if unspecified.
+     *
+     * @see #GridExtent(GridExtent)
      */
     private GridExtent(final int dimension, final DimensionNameType[] axisTypes) {
         coordinates = allocate(dimension);
@@ -211,63 +215,6 @@ public class GridExtent implements Serializable {
     }
 
     /**
-     * Creates a new grid extent with low and high values adjusted by dividing the {@linkplain #getSize(int) size}
-     * by the given strides. The policy of dividing the lower coordinates by the stride shall be kept consistent
-     * with {@link GridChange#getTargetGeometry(int...)} computation of grid to CRS.
-     *
-     * <div class="note"><b>Note:</b>
-     * if a division does not produce an integer, then the size is rounded toward 0 (or toward negative infinity since
-     * sizes are always positive numbers). But the envelope computed by {@link GridChange#getTargetGeometry(int...)}
-     * will nevertheless be larger. This counter-intuitive effect is because the "grid to CRS" transform is scaled by
-     * the same factors, which results in larger cells. Assuming:
-     *
-     * <ul>
-     *   <li>All {@linkplain #getLow(int) low coordinates} = 0.</li>
-     *   <li>{@linkplain #getHigh(int) High coordinates} before scaling denoted <var>h₁</var>.</li>
-     *   <li>High coordinates after scaling denoted <var>h₂</var>.</li>
-     *   <li>Scale factor (or sub-sampling) <var>s</var> is an integer ≧ 1.</li>
-     * </ul>
-     *
-     * Then the envelope upper bounds <var>x</var> is:
-     *
-     * <ul>
-     *   <li>x = (h₁ + 1) × c</li>
-     *   <li>x = (h₂ + f) × c⋅s   which implies   h₂ = h₁/s   and   f = 1/s</li>
-     * </ul>
-     *
-     * If we modify the later equation for integer division instead than real numbers, we have:
-     *
-     * <blockquote>x = (h₂ + f) × c⋅s   where   h₂ = floor(h₁/s)   and   f = ((h₁ mod s) + 1)/s</blockquote>
-     *
-     * Because <var>s</var> ≧ 1, then <var>f</var> ≦ 1. But the <var>f</var> actually used by {@link #toCRS(MathTransform,
-     * MathTransform)} is hard-coded to 1 since it assumes that all cells are whole, i.e. it does not take in account that
-     * the last cell may actually be fraction of a cell. Since 1 ≧ <var>f</var>, the computed envelope may be larger.</div>
-     *
-     * @param  extent   the extent from which to compute a new extent.
-     * @param  strides  the strides. Length shall be equal to the number of dimension and all values shall be greater than zero.
-     * @throws IllegalArgumentException if a stride is not greater than zero.
-     */
-    GridExtent(final GridExtent extent, final int[] strides) {
-        types = extent.types;
-        final int m = extent.getDimension();
-        coordinates = new long[m << 1];
-        for (int i=0; i<m; i++) {
-            final int s = strides[i];
-            if (s <= 0) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueNotGreaterThanZero_2, "strides[" + i + ']', s));
-            }
-            final int j = i + m;
-            long low  = extent.coordinates[i];
-            long size = extent.coordinates[j] - low + 1;                                       // Result is an unsigned number.
-            if (size == 0) throw new ArithmeticException("long overflow");
-            long r = Long.divideUnsigned(size, s);
-            if (r*s == size) r--;                           // Make inclusive if the division did not already rounded toward 0.
-            coordinates[i] = low /= s;
-            coordinates[j] = low + r;
-        }
-    }
-
-    /**
      * Creates a new grid extent for an image or matrix of the given size.
      * The {@linkplain #getLow() low} grid coordinates are zeros and the axis types are
      * {@link DimensionNameType#COLUMN} and {@link DimensionNameType#ROW ROW} in that order.
@@ -313,6 +260,7 @@ public class GridExtent implements Serializable {
      *
      * @see #getLow()
      * @see #getHigh()
+     * @see #append(DimensionNameType, long, long, boolean)
      */
     public GridExtent(final DimensionNameType[] axisTypes, final long[] low, final long[] high, final boolean isHighIncluded) {
         ArgumentChecks.ensureNonNull("high", high);
@@ -333,8 +281,8 @@ public class GridExtent implements Serializable {
                 coordinates[i] = Math.decrementExact(coordinates[i]);
             }
         }
-        checkCoherence(coordinates);
         types = validateAxisTypes(axisTypes);
+        validateCoordinates();
     }
 
     /**
@@ -352,8 +300,10 @@ public class GridExtent implements Serializable {
      * @param  enclosing           if the new grid is a sub-grid of a larger grid, that larger grid. Otherwise {@code null}.
      * @param  modifiedDimensions  if {@code enclosing} is non-null, the grid dimensions to set from the envelope.
      *                             The length of this array shall be equal to the {@code envelope} dimension.
+     *                             This argument is ignored if {@code enclosing} is null.
      *
      * @see #toCRS(MathTransform, MathTransform)
+     * @see #slice(DirectPosition, int[])
      */
     GridExtent(final AbstractEnvelope envelope, final GridRoundingMode rounding, final int[] margin,
             final GridExtent enclosing, final int[] modifiedDimensions)
@@ -434,7 +384,7 @@ public class GridExtent implements Serializable {
                 }
             } else if (enclosing == null || !Double.isNaN(min) || !Double.isNaN(max)) {
                 throw new IllegalArgumentException(Resources.format(
-                        Resources.Keys.IllegalGridEnvelope_3, i, min, max));
+                        Resources.Keys.IllegalGridEnvelope_3, getAxisIdentification(i,i), min, max));
             }
             /*
              * We do not throw an exception if 'enclosing' is non-null and envelope bounds are NaN
@@ -472,6 +422,19 @@ public class GridExtent implements Serializable {
     }
 
     /**
+     * Creates a copy of the given grid extent. The {@link #coordinates} array is cloned
+     * by the {@link #types} array is shared between the two instances. This constructor
+     * is reserved to methods that modify the coordinates after construction. It must be
+     * private since we do not allow coordinates modifications by public API.
+     *
+     * @see #GridExtent(int, DimensionNameType[])
+     */
+    private GridExtent(final GridExtent extent) {
+        types = extent.types;
+        coordinates = extent.coordinates.clone();
+    }
+
+    /**
      * Creates a new grid envelope as a copy of the given one.
      *
      * @param  extent  the grid envelope to copy.
@@ -488,8 +451,8 @@ public class GridExtent implements Serializable {
             coordinates[i] = extent.getLow(i);
             coordinates[i + dimension] = extent.getHigh(i);
         }
-        checkCoherence(coordinates);
         types = (extent instanceof GridExtent) ? ((GridExtent) extent).types : null;
+        validateCoordinates();
     }
 
     /**
@@ -655,6 +618,23 @@ public class GridExtent implements Serializable {
     }
 
     /**
+     * Returns the axis number followed by the localized axis type if available.
+     * This is used for error messages only.
+     *
+     * @param  index       index of the dimension as stored in this grid extent.
+     * @param  indexShown  index to write in the message. Often the same as {@code index}.
+     */
+    private Object getAxisIdentification(final int index, final int indexShown) {
+        if (types != null) {
+            final DimensionNameType type = types[index];
+            if (type != null) {
+                return indexShown + " (" + Types.getCodeTitle(type) + ')';
+            }
+        }
+        return indexShown;
+    }
+
+    /**
      * Transforms this grid extent to a "real world" envelope using the given transform.
      * The transform shall map <em>cell corner</em> to real world coordinates.
      *
@@ -765,7 +745,7 @@ public class GridExtent implements Serializable {
         System.arraycopy(coordinates, dimension, ex.coordinates, newDim, dimension);
         ex.coordinates[dimension]          = low;
         ex.coordinates[dimension + newDim] = high;
-        checkCoherence(ex.coordinates);
+        ex.validateCoordinates();
         return ex;
     }
 
@@ -798,6 +778,91 @@ public class GridExtent implements Serializable {
     }
 
     /**
+     * Creates a new grid extent sub-sampled by the given amount of cells along each grid dimensions.
+     * This method divides {@linkplain #getLow(int) low coordinates} and {@linkplain #getSize(int) grid sizes}
+     * by the given strides, rounding toward zero. The {@linkplain #getHigh(int) high coordinates} are adjusted
+     * accordingly (this is often equivalent to dividing high coordinates by the strides too, but a difference
+     * of one cell may exist).
+     *
+     * <div class="note"><b>Note:</b>
+     * The envelope computed from a grid extent may become <em>larger</em> after sub-sampling, not smaller.
+     * This effect can be understood intuitively if we consider that cells become larger after sub-sampling,
+     * which implies that accurate representation of the same envelope may require fractional cells on some
+     * grid borders.</div>
+     *
+     * @param  strides  the strides. Length shall be equal to the number of dimension and all values shall be greater than zero.
+     * @return the sub-sampled extent, or {@code this} is sub-sampling results in the same extent.
+     * @throws IllegalArgumentException if a stride is not greater than zero.
+     *
+     * @see GridGeometry#subgrid(Envelope, double...)
+     */
+    public GridExtent subsample(final int... strides) {
+        ArgumentChecks.ensureNonNull("strides", strides);
+        final int m = getDimension();
+        ArgumentChecks.ensureDimensionMatches("strides", m, strides);
+        final GridExtent sub = new GridExtent(this);
+        for (int i=0; i<m; i++) {
+            final int s = strides[i];
+            if (s > 1) {
+                final int j = i + m;
+                long low  = coordinates[i];
+                long size = coordinates[j] - low + 1;                                             // Result is an unsigned number.
+                if (size == 0) throw new ArithmeticException("long overflow");
+                long r = Long.divideUnsigned(size, s);
+                if (r*s == size) r--;                           // Make inclusive if the division did not already rounded toward 0.
+                sub.coordinates[i] = low /= s;
+                sub.coordinates[j] = low + r;
+            } else if (s <= 0) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueNotGreaterThanZero_2, "strides[" + i + ']', s));
+            }
+        }
+        return Arrays.equals(coordinates, sub.coordinates) ? this : sub;
+    }
+
+    /**
+     * Creates a new grid extent which represent a slice of this grid at the given point.
+     * The given point may have less dimensions than this grid extent, in which case the
+     * dimensions must be specified in the {@code modifiedDimensions} array. Coordinates
+     * of the given point will be rounded to nearest integer.
+     *
+     * @param  slicePoint           where to take a slice. NaN values are handled as if their dimensions were absent.
+     * @param  modifiedDimensions   mapping from {@code slicePoint} dimensions to this {@code GridExtent} dimensions,
+     *                              or {@code null} if {@code slicePoint} contains all grid dimensions in same order.
+     * @return a grid extent for the specified slice.
+     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
+     */
+    final GridExtent slice(final DirectPosition slicePoint, final int[] modifiedDimensions) {
+        final GridExtent slice = new GridExtent(this);
+        final int n = slicePoint.getDimension();
+        final int m = getDimension();
+        for (int k=0; k<n; k++) {
+            double p = slicePoint.getOrdinate(k);
+            if (!Double.isNaN(p)) {
+                final long c = Math.round(p);
+                final int i = (modifiedDimensions != null) ? modifiedDimensions[k] : k;
+                final long low  = coordinates[i];
+                final long high = coordinates[i + m];
+                if (c >= low && c <= high) {
+                    slice.coordinates[i + m] = slice.coordinates[i] = c;
+                } else {
+                    final StringBuilder b = new StringBuilder();
+                    String separator = "";
+                    for (int j=0; j<n; j++) {
+                        p = slicePoint.getOrdinate(j);
+                        if (!Double.isNaN(p)) {
+                            b.append(separator).append(Math.round(p));
+                            separator = ", ";
+                        }
+                    }
+                    throw new PointOutsideCoverageException(Resources.format(Resources.Keys.GridCoordinateOutsideCoverage_4,
+                            getAxisIdentification(i,k), low, high, b.toString()));
+                }
+            }
+        }
+        return Arrays.equals(coordinates, slice.coordinates) ? this : slice;
+    }
+
+    /**
      * Returns a hash value for this grid envelope. This value need not remain
      * consistent between different implementations of the same class.
      *
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 853c54e..a76ca6e 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -223,10 +223,7 @@ public class GridGeometry implements Serializable {
      *
      * The new {@linkplain #getEnvelope() grid geometry envelope} will be {@linkplain GeneralEnvelope#intersect(Envelope)
      * clipped} to the envelope of the other grid geometry. This is for preventing the envelope to become larger under the
-     * effect of sub-sampling (because each cell become larger).
-     *
-     * <p>This constructor is for subclasses that wish to specialize their {@link #subgrid(Envelope, double...)} or related
-     * methods. Users should invoke {@code GridGeometry} member methods or use {@link GridChange} instead.</p>
+     * effect of sub-sampling (because {@linkplain GridExtent#subsample(int...) each cell become larger}).
      *
      * @param  other    the other grid geometry to copy.
      * @param  extent   the new extent for the grid geometry to construct, or {@code null} if none.
@@ -237,13 +234,11 @@ public class GridGeometry implements Serializable {
      * @see #getExtent(Envelope)
      * @see #subgrid(Envelope, double...)
      */
-    protected GridGeometry(final GridGeometry other, final GridExtent extent, final MathTransform toOther) throws TransformException {
+    GridGeometry(final GridGeometry other, final GridExtent extent, final MathTransform toOther) throws TransformException {
         ArgumentChecks.ensureNonNull("other", other);
         final int dimension = other.getDimension();
         this.extent = extent;
-        if (extent != null) {
-            ensureDimensionMatches("extent", dimension, extent.getDimension());
-        }
+        ensureDimensionMatches(dimension, extent);
         if (toOther == null || toOther.isIdentity()) {
             gridToCRS   = other.gridToCRS;
             cornerToCRS = other.cornerToCRS;
@@ -311,12 +306,8 @@ public class GridGeometry implements Serializable {
             final CoordinateReferenceSystem crs) throws TransformException
     {
         if (gridToCRS != null) {
-            if (extent != null) {
-                ensureDimensionMatches("extent", gridToCRS.getSourceDimensions(), extent.getDimension());
-            }
-            if (crs != null) {
-                ensureDimensionMatches("crs", gridToCRS.getTargetDimensions(), crs.getCoordinateSystem().getDimension());
-            }
+            ensureDimensionMatches(gridToCRS.getSourceDimensions(), extent);
+            ArgumentChecks.ensureDimensionMatches("crs", gridToCRS.getTargetDimensions(), crs);
         } else if (crs == null) {
             ArgumentChecks.ensureNonNull("extent", extent);
         }
@@ -398,8 +389,8 @@ public class GridGeometry implements Serializable {
     {
         if (gridToCRS == null) {
             ArgumentChecks.ensureNonNull("envelope", envelope);
-        } else if (envelope != null) {
-            ensureDimensionMatches("envelope", gridToCRS.getTargetDimensions(), envelope.getDimension());
+        } else {
+            ArgumentChecks.ensureDimensionMatches("envelope", gridToCRS.getTargetDimensions(), envelope);
         }
         ArgumentChecks.ensureNonNull("rounding", rounding);
         this.gridToCRS   = PixelTranslation.translate(gridToCRS, anchor, PixelInCell.CELL_CENTER);
@@ -429,17 +420,20 @@ public class GridGeometry implements Serializable {
 
     /**
      * Ensures that the given dimension is equals to the expected value. If not, throws an exception.
+     * This method assumes that the argument name is {@code "extent"}.
      *
-     * @param argument  the name of the argument being tested.
+     * @param extent    the extent to validate, or {@code null} if none.
      * @param expected  the expected number of dimension.
-     * @param dimension the actual dimension of the argument value.
      */
-    private static void ensureDimensionMatches(final String argument, final int expected, final int dimension)
+    private static void ensureDimensionMatches(final int expected, final GridExtent extent)
             throws MismatchedDimensionException
     {
-        if (dimension != expected) {
-            throw new MismatchedDimensionException(Errors.format(
-                    Errors.Keys.MismatchedDimension_3, argument, expected, dimension));
+        if (extent != null) {
+            final int dimension = extent.getDimension();
+            if (dimension != expected) {
+                throw new MismatchedDimensionException(Errors.format(
+                        Errors.Keys.MismatchedDimension_3, "extent", expected, dimension));
+            }
         }
     }
 
@@ -575,7 +569,9 @@ public class GridGeometry implements Serializable {
 
     /**
      * Returns the coordinate range of the grid intersecting the given spatiotemporal region.
-     * The given envelope can be expressed in any coordinate reference system (CRS).
+     * The given envelope does not need to be expressed in the same coordinate reference system
+     * (CRS) than {@linkplain #getCoordinateReferenceSystem() the one of this grid geometry};
+     * coordinate conversions or transformations will be applied as needed.
      * That envelope CRS may have fewer dimensions than this grid geometry CRS,
      * in which case grid dimensions not mapped to envelope dimensions will be returned unchanged.
      * In other words, the {@code GridGeometry} dimensions not found in the given {@code areaOfInterest}
@@ -590,7 +586,6 @@ public class GridGeometry implements Serializable {
      * @throws TransformException if an error occurred while converting the envelope coordinates to grid coordinates.
      *
      * @see #subgrid(Envelope, double...)
-     * @see #GridGeometry(GridGeometry, GridExtent, MathTransform)
      */
     public GridExtent getExtent(final Envelope areaOfInterest) throws IncompleteGridGeometryException, TransformException {
         ArgumentChecks.ensureNonNull("areaOfInterest", areaOfInterest);
@@ -903,7 +898,9 @@ public class GridGeometry implements Serializable {
 
     /**
      * Returns a grid geometry over a sub-region of this grid geometry and optionally with sub-sampling.
-     * The given envelope can be expressed in any coordinate reference system (CRS) accepted by {@link #getExtent(Envelope)}.
+     * The given envelope does not need to be expressed in the same coordinate reference system
+     * (CRS) than {@linkplain #getCoordinateReferenceSystem() the one of this grid geometry};
+     * coordinate conversions or transformations will be applied as needed.
      * The target resolution, if provided, shall be in same units than the given envelope with axes in the same order.
      * If the length of {@code targetResolution} array is less than the number of dimensions of {@code areaOfInterest},
      * then no sub-sampling will be applied on the missing dimensions.
@@ -917,6 +914,7 @@ public class GridGeometry implements Serializable {
      * @throws TransformException if an error occurred while converting the envelope coordinates to grid coordinates.
      *
      * @see #getExtent(Envelope)
+     * @see GridExtent#subsample(int...)
      */
     public GridGeometry subgrid(final Envelope areaOfInterest, double... targetResolution) throws TransformException {
         if (extent == null) {
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
index 60ab1b3..302f4dc 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/SubgridCalculator.java
@@ -17,14 +17,17 @@
 package org.apache.sis.coverage.grid;
 
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.internal.referencing.DirectPositionView;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -32,6 +35,9 @@ import org.apache.sis.internal.raster.Resources;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
 
+// Branch-dependent imports
+import org.opengis.coverage.PointOutsideCoverageException;
+
 
 /**
  * Helper class for computing the grid extent of a sub-area of a given grid geometry.
@@ -56,6 +62,48 @@ final class SubgridCalculator {
     MathTransform toSubsampled;
 
     /**
+     * List of grid dimensions that are modified by the {@code cornerToCRS} transform, or null for all dimensions.
+     * The length of this array is the number of dimensions of the given Area Of Interest (AOI). Each value in this
+     * array is between 0 inclusive and {@code extent.getDimension()} exclusive.
+     */
+    private int[] modifiedDimensions;
+
+    /**
+     * Computes the sub-grid for a slice at the given slice point. The given position can be given in any CRS.
+     * The position should not define a coordinate for all dimensions, otherwise the sub-grid would degenerate
+     * to a single point. Dimensions can be left unspecified either by assigning to the position a CRS without
+     * those dimensions, or by assigning the NaN value to some coordinates.
+     *
+     * @param  grid         the enclosing grid geometry (mandatory).
+     * @param  cornerToCRS  the transform from cell corners to grid CRS (mandatory).
+     * @param  slicePoint   the coordinates where to get a slice.
+     * @throws TransformException if an error occurred while converting the envelope coordinates to grid coordinates.
+     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
+     */
+    SubgridCalculator(final GridGeometry grid, MathTransform cornerToCRS, final DirectPosition slicePoint)
+            throws TransformException
+    {
+        try {
+            if (grid.envelope != null) {
+                final CoordinateReferenceSystem sourceCRS = grid.envelope.getCoordinateReferenceSystem();
+                if (sourceCRS != null) {
+                    final CoordinateReferenceSystem targetCRS = slicePoint.getCoordinateReferenceSystem();
+                    if (targetCRS != null) {
+                        final CoordinateOperation operation = CRS.findOperation(sourceCRS, targetCRS, null);
+                        cornerToCRS = MathTransforms.concatenate(cornerToCRS, operation.getMathTransform());
+                    }
+                }
+            }
+            final int dimension = cornerToCRS.getTargetDimensions();
+            ArgumentChecks.ensureDimensionMatches("slicePoint", dimension, slicePoint);
+            cornerToCRS = dropUnusedDimensions(cornerToCRS, dimension);
+        } catch (FactoryException e) {
+            throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions), e);
+        }
+        extent = grid.extent.slice(cornerToCRS.transform(slicePoint, null), modifiedDimensions);
+    }
+
+    /**
      * Computes the sub-grid over the given area of interest with the given resolution.
      * At least one of {@code areaOfInterest} and {@code resolution} shall be non-null.
      * It is caller's responsibility to ensure that {@link GridGeometry#extent} is non-null.
@@ -70,12 +118,6 @@ final class SubgridCalculator {
     SubgridCalculator(final GridGeometry grid, MathTransform cornerToCRS, final Envelope areaOfInterest, double[] resolution)
             throws TransformException
     {
-        /*
-         * List of grid dimensions that are modified by the 'cornerToCRS' transform, or null for all dimensions.
-         * The length of this array is the number of dimensions of the given Area Of Interest (AOI). Each value
-         * in this array is between 0 inclusive and 'extent.getDimension()' exclusive.
-         */
-        int[] modifiedDimensions = null;
         try {
             /*
              * If the envelope CRS is different than the expected CRS, concatenate the envelope transformation
@@ -92,15 +134,7 @@ final class SubgridCalculator {
              */
             final int dimension = cornerToCRS.getTargetDimensions();
             ArgumentChecks.ensureDimensionMatches("areaOfInterest", dimension, areaOfInterest);
-            if (dimension < cornerToCRS.getSourceDimensions()) {
-                final TransformSeparator sep = new TransformSeparator(cornerToCRS);
-                sep.setTrimSourceDimensions(true);
-                cornerToCRS = sep.separate();
-                modifiedDimensions = sep.getSourceDimensions();
-                if (modifiedDimensions.length != dimension) {
-                    throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
-                }
-            }
+            cornerToCRS = dropUnusedDimensions(cornerToCRS, dimension);
         } catch (FactoryException e) {
             throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions), e);
         }
@@ -114,7 +148,7 @@ final class SubgridCalculator {
         GeneralEnvelope indices = null;
         if (areaOfInterest != null) {
             indices = Envelopes.transform(cornerToCRS.inverse(), areaOfInterest);
-            setExtent(indices, extent, modifiedDimensions);
+            setExtent(indices, extent);
         }
         if (indices == null || indices.getDimension() != dimension) {
             indices = new GeneralEnvelope(dimension);
@@ -139,6 +173,7 @@ final class SubgridCalculator {
          */
         if (resolution != null && resolution.length != 0) {
             resolution = ArraysExt.resize(resolution, cornerToCRS.getTargetDimensions());
+            final int[] modifiedDimensions = this.modifiedDimensions;                     // Will not change anymore.
             Matrix m = cornerToCRS.derivative(new DirectPositionView.Double(getPointOfInterest(modifiedDimensions)));
             resolution = Matrices.inverse(m).multiply(resolution);
             boolean modified = false;
@@ -160,7 +195,7 @@ final class SubgridCalculator {
              */
             if (modified) {
                 final GridExtent unscaled = extent;
-                setExtent(indices, null, null);
+                setExtent(indices, null);
                 m = Matrices.createIdentity(dimension + 1);
                 for (int k=0; k<resolution.length; k++) {
                     final double s = resolution[k];
@@ -176,13 +211,35 @@ final class SubgridCalculator {
     }
 
     /**
+     * Drops the source dimensions that are not needed for producing the target dimensions.
+     * The retained source dimensions are stored in {@link #modifiedDimensions}.
+     * This method is invoked in an effort to make the transform invertible.
+     *
+     * @param  cornerToCRS  transform from grid coordinates to AOI coordinates.
+     * @param  dimension    value of {@code cornerToCRS.getTargetDimensions()}.
+     */
+    private MathTransform dropUnusedDimensions(MathTransform cornerToCRS, final int dimension)
+            throws FactoryException, TransformException
+    {
+        if (dimension < cornerToCRS.getSourceDimensions()) {
+            final TransformSeparator sep = new TransformSeparator(cornerToCRS);
+            sep.setTrimSourceDimensions(true);
+            cornerToCRS = sep.separate();
+            modifiedDimensions = sep.getSourceDimensions();
+            if (modifiedDimensions.length != dimension) {
+                throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
+            }
+        }
+        return cornerToCRS;
+    }
+
+    /**
      * Sets {@link #extent} to the given envelope, rounded to nearest integers.
      *
-     * @param  indices             the envelope to use for setting the grid extent.
-     * @param  enclosing           the enclosing grid extent if a sub-sampling is not yet applied, {@code null} otherwise.
-     * @param  modifiedDimensions  if {@code enclosing} is non-null, the grid dimensions to set from the envelope.
+     * @param  indices    the envelope to use for setting the grid extent.
+     * @param  enclosing  the enclosing grid extent if a sub-sampling is not yet applied, {@code null} otherwise.
      */
-    private void setExtent(final GeneralEnvelope indices, final GridExtent enclosing, final int[] modifiedDimensions) {
+    private void setExtent(final GeneralEnvelope indices, final GridExtent enclosing) {
         final GridExtent sub = new GridExtent(indices, GridRoundingMode.NEAREST, null, enclosing, modifiedDimensions);
         if (!sub.equals(extent)) {
             extent = sub;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/image/DefaultIterator.java b/core/sis-raster/src/main/java/org/apache/sis/image/DefaultIterator.java
index cd5f58e..7450cbf 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/image/DefaultIterator.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/image/DefaultIterator.java
@@ -178,7 +178,7 @@ final class DefaultIterator extends WritablePixelIterator {
     @Override
     public void moveTo(final int px, final int py) {
         if (px < lowerX || px >= upperX ||  py < lowerY || py >= upperY) {
-            throw new IndexOutOfBoundsException(Resources.format(Resources.Keys.CoordinateOutsideDomain_2, px, py));
+            throw new IndexOutOfBoundsException(Resources.format(Resources.Keys.OutOfIteratorDomain_2, px, py));
         }
         if (image != null) {
             final int tx = Math.floorDiv(px - tileGridXOffset, tileWidth);
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
index 26fd9a5..f21386a 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
@@ -49,6 +49,13 @@ import org.apache.sis.util.collection.WeakValueHashMap;
  */
 public final class ColorModelFactory {
     /**
+     * Applies a gray scale to quantitative category and transparent colors to qualitative categories.
+     * This is a possible argument for {@link #createColorModel(List, int, int, Function)}.
+     */
+    public static final Function<Category,Color[]> GRAYSCALE =
+            (category) -> category.isQuantitative() ? new Color[] {Color.BLACK, Color.WHITE} : null;
+
+    /**
      * Shared instances of {@link ColorModel}s. Maintaining shared instance is not that much interesting
      * for most kind of color models, except {@link IndexColorModel} which can potentially be quite big.
      * This class works for all color models because they were no technical reasons to restrict, but the
@@ -277,8 +284,10 @@ public final class ColorModelFactory {
      * @return a color model suitable for {@link java.awt.image.RenderedImage} objects with values in the given ranges.
      */
     public static ColorModel createColorModel(final List<? extends SampleDimension> bands,
-            final int visibleBand, final int type, final Function<Category,Color[]> colors)
+            final int visibleBand, final int type, Function<Category,Color[]> colors)
     {
+        ArgumentChecks.ensureNonNull("bands",  bands);
+        ArgumentChecks.ensureNonNull("colors", colors);
         final Map<NumberRange<?>, Color[]> ranges = new LinkedHashMap<>();
         for (final Category category : bands.get(visibleBand).getCategories()) {
             ranges.put(category.getSampleRange(), colors.apply(category));
@@ -426,9 +435,12 @@ public final class ColorModelFactory {
             int combined = 0;
             final int[] ARGB = new int[colors.length];
             for (int i=0; i<ARGB.length; i++) {
-                int color = colors[i].getRGB();                     // Note: getRGB() is really getARGB().
-                combined |= color;
-                ARGB[i]   = color;
+                final Color color = colors[i];
+                if (color != null) {
+                    int c = color.getRGB();                         // Note: getRGB() is really getARGB().
+                    combined |= c;
+                    ARGB[i]   = c;
+                }
             }
             if ((combined & 0xFF000000) != 0) {
                 return ARGB;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
index a961474..52edab2 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
@@ -79,9 +79,10 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CategoryRangeOverlap_4 = 13;
 
         /**
-         * The ({0}, {1}) pixel coordinate is outside iterator domain.
+         * Indices ({3}) are outside grid coverage. The value at dimension {0} shall be between {1} and
+         * {2} inclusive.
          */
-        public static final short CoordinateOutsideDomain_2 = 1;
+        public static final short GridCoordinateOutsideCoverage_4 = 21;
 
         /**
          * Sample value range {1} for “{0}” category is illegal.
@@ -139,6 +140,16 @@ public final class Resources extends IndexedResourceBundle {
         public static final short NonLinearInDimensions_1 = 20;
 
         /**
+         * The ({0}, {1}) pixel coordinate is outside iterator domain.
+         */
+        public static final short OutOfIteratorDomain_2 = 1;
+
+        /**
+         * Point ({0}) is outside the coverage domain.
+         */
+        public static final short PointOutsideCoverageDomain_1 = 22;
+
+        /**
          * Too many qualitative categories.
          */
         public static final short TooManyQualitatives = 17;
@@ -202,8 +213,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replace all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -217,7 +228,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replace all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -234,7 +245,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replace all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
@@ -251,4 +262,25 @@ public final class Resources extends IndexedResourceBundle {
     {
         return forLocale(null).getString(key, arg0, arg1, arg2);
     }
+
+    /**
+     * Gets a string for the given key and replace all occurrence of "{0}",
+     * "{1}", with values of {@code arg0}, {@code arg1}, etc.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @param  arg1  value to substitute to "{1}".
+     * @param  arg2  value to substitute to "{2}".
+     * @param  arg3  value to substitute to "{3}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be found.
+     */
+    public static String format(final short  key,
+                                final Object arg0,
+                                final Object arg1,
+                                final Object arg2,
+                                final Object arg3) throws MissingResourceException
+    {
+        return forLocale(null).getString(key, arg0, arg1, arg2, arg3);
+    }
 }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
index a8e6193..e8e3789 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
@@ -23,7 +23,7 @@ CanNotEnumerateValuesInRange_1    = Can not enumerate values in the {0} range.
 CanNotMapToGridDimensions         = Some envelope dimensions can not be mapped to grid dimensions.
 CanNotSimplifyTransferFunction_1  = Can not simplify transfer function of sample dimension \u201c{0}\u201d.
 CategoryRangeOverlap_4            = The two categories \u201c{0}\u201d and \u201c{2}\u201d have overlapping ranges: {1} and {3} respectively.
-CoordinateOutsideDomain_2         = The ({0}, {1}) pixel coordinate is outside iterator domain.
+GridCoordinateOutsideCoverage_4   = Indices ({3}) are outside grid coverage. The value at dimension {0} shall be between {1} and {2} inclusive.
 IllegalCategoryRange_2            = Sample value range {1} for \u201c{0}\u201d category is illegal.
 IllegalGridEnvelope_3             = Illegal grid envelope [{1} \u2026 {2}] for dimension {0}.
 IllegalTransferFunction_1         = Illegal transfer function for \u201c{0}\u201d category.
@@ -35,6 +35,8 @@ MismatchedSampleModel             = The two images use different sample models.
 MismatchedTileGrid                = The two images have different tile grid.
 NoCategoryForValue_1              = No category for value {0}.
 NonLinearInDimensions_1           = non-linear in {0} dimension{0,choice,1#|2#s}:
+OutOfIteratorDomain_2             = The ({0}, {1}) pixel coordinate is outside iterator domain.
+PointOutsideCoverageDomain_1      = Point ({0}) is outside the coverage domain.
 TooManyQualitatives               = Too many qualitative categories.
 UnspecifiedCRS                    = Coordinate reference system is unspecified.
 UnspecifiedGridExtent             = Grid extent is unspecified.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
index 5bb7c9e..6b580ee 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
@@ -28,7 +28,7 @@ CanNotEnumerateValuesInRange_1    = Ne peut pas \u00e9num\u00e9rer les valeurs d
 CanNotMapToGridDimensions         = Certaines dimensions de l\u2019enveloppe ne correspondent pas \u00e0 des dimensions de la grille.
 CanNotSimplifyTransferFunction_1  = Ne peut pas simplifier la fonction de transfert de la dimension d\u2019\u00e9chantillonnage \u00ab\u202f{0}\u202f\u00bb.
 CategoryRangeOverlap_4            = Les deux cat\u00e9gories \u00ab\u202f{0}\u202f\u00bb et \u00ab\u202f{2}\u202f\u00bb ont des plages de valeurs qui se chevauchent\u2008: {1} et {3} respectivement.
-CoordinateOutsideDomain_2         = La coordonn\u00e9e pixel ({0}, {1}) est en dehors du domaine de l\u2019it\u00e9rateur.
+GridCoordinateOutsideCoverage_4   = Les indices ({3}) sont en dehors du domaine de la grille. La valeur \u00e0 la dimension {0} doit \u00eatre entre {1} et {2} inclusivement.
 IllegalCategoryRange_2            = La plage de valeurs {1} pour la cat\u00e9gorie \u00ab\u202f{0}\u202f\u00bb est ill\u00e9gale.
 IllegalGridEnvelope_3             = La plage d\u2019index [{1} \u2026 {2}] de la dimension {0} n\u2019est pas valide.
 IllegalTransferFunction_1         = Fonction de transfert ill\u00e9gale pour la cat\u00e9gorie \u00ab\u202f{0}\u202f\u00bb.
@@ -40,6 +40,8 @@ MismatchedSampleModel             = Les deux images disposent les pixels diff\u0
 MismatchedTileGrid                = Les deux images utilisent des grilles de tuiles diff\u00e9rentes.
 NoCategoryForValue_1              = Aucune cat\u00e9gorie n\u2019est d\u00e9finie pour la valeur {0}.
 NonLinearInDimensions_1           = non-lin\u00e9aire dans {0} dimension{0,choice,1#|2#s}\u2008:
+OutOfIteratorDomain_2             = La coordonn\u00e9e pixel ({0}, {1}) est en dehors du domaine de l\u2019it\u00e9rateur.
+PointOutsideCoverageDomain_1      = Le point ({0}) est en dehors du domaine de la couverture de donn\u00e9es.
 TooManyQualitatives               = Trop de cat\u00e9gories qualitatives.
 UnspecifiedCRS                    = Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es n\u2019a pas \u00e9t\u00e9 sp\u00e9cifi\u00e9.
 UnspecifiedGridExtent             = L\u2019\u00e9tendue de la grille n\u2019a pas \u00e9t\u00e9 sp\u00e9cifi\u00e9e.
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index a6a4d29..6588e80 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -55,12 +55,12 @@ public final strictfp class GridExtentTest extends TestCase {
     }
 
     /**
-     * Tests the {@link GridExtent#GridExtent(GridExtent, int[])} constructor.
+     * Tests the {@link GridExtent#subsample(int...)}.
      */
     @Test
-    public void testCreateFromStrides() {
+    public void testSubsample() {
         GridExtent extent = create3D();
-        extent = new GridExtent(extent, new int[] {4, 3, 9});
+        extent = extent.subsample(4, 3, 9);
         assertExtentEquals(extent, 0, 25, 124);                 // 100 cells
         assertExtentEquals(extent, 1, 66, 265);                 // 200 cells
         assertExtentEquals(extent, 2,  4,   5);                 //   2 cells
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java
index 7f05311..9d9cf42 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/internal/gazetteer/Resources.java
@@ -194,8 +194,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -209,7 +209,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -226,7 +226,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
index 8857132..a5441de 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
@@ -83,6 +83,7 @@ public final class ReferencingUtilities extends Static {
      * @return the prime meridian in the given units, or {@code 0} if the given prime meridian was null.
      *
      * @see DefaultPrimeMeridian#getGreenwichLongitude(Unit)
+     * @see org.apache.sis.referencing.CRS#getGreenwichLongitude(GeodeticCRS)
      */
     public static double getGreenwichLongitude(final PrimeMeridian primeMeridian, final Unit<Angle> unit) {
         if (primeMeridian == null) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
index 02c9f59..bb81dae 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
@@ -549,8 +549,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -564,7 +564,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -581,7 +581,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
@@ -600,7 +600,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
index 2fc0376..4115099 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
@@ -33,6 +33,7 @@ import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CSAuthorityFactory;
 import org.opengis.referencing.AuthorityFactory;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.IdentifiedObjects;
@@ -434,7 +435,7 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
     static IllegalArgumentException unexpectedDimension(final Map<String,?> properties,
             final CoordinateSystemAxis[] axes, final int expected)
     {
-        return new IllegalArgumentException(Errors.getResources(properties).getString(
+        return new MismatchedDimensionException(Errors.getResources(properties).getString(
                 Errors.Keys.MismatchedDimension_3, "filter(cs)", expected, axes.length));
     }
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
index e61c6ba..5a76f86 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
@@ -24,7 +24,6 @@ import java.io.ObjectInputStream;
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import org.opengis.geometry.Envelope;
-import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -214,10 +213,7 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
         ArgumentChecks.ensureNonNull("gridSize",         gridSize);
         ArgumentChecks.ensureNonNull("translationUnit",  translationUnit);
         int n = coordinateToGrid.getTargetDimensions();
-        if (n != gridSize.length) {
-            throw new MismatchedDimensionException(Errors.format(
-                    Errors.Keys.MismatchedDimension_3, "gridSize", n, gridSize.length));
-        }
+        ArgumentChecks.ensureDimensionMatches("gridSize", n, gridSize);
         this.coordinateUnit   = coordinateUnit;
         this.coordinateToGrid = coordinateToGrid;
         this.isCellValueRatio = isCellValueRatio;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
index 67b5846..647ed97 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
@@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.FactoryException;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.ConcatenatedOperation;
@@ -208,7 +209,7 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp
                 final int dim1 = previous.getCoordinateSystem().getDimension();
                 final int dim2 = next.getCoordinateSystem().getDimension();
                 if (dim1 != dim2) {
-                    throw new IllegalArgumentException(Errors.getResources(properties).getString(
+                    throw new MismatchedDimensionException(Errors.getResources(properties).getString(
                             Errors.Keys.MismatchedDimension_3, "operations[" + i + "].sourceCRS", dim1, dim2));
                 }
             }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
index 24493c3..db73058 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
@@ -311,8 +311,9 @@ search: for (int j=numPoints; --j >= 0;) {
      * that positions are stored in this builder as they are read from user-provided collection,
      * with {@link #numPoints} the index of the next point that we failed to add.
      */
-    private String mismatchedDimension(final String name, final int expected, final int actual) {
-        return Errors.format(Errors.Keys.MismatchedDimension_3, name + '[' + numPoints + ']', expected, actual);
+    private MismatchedDimensionException mismatchedDimension(final String name, final int expected, final int actual) {
+        return new MismatchedDimensionException(Errors.format(
+                Errors.Keys.MismatchedDimension_3, name + '[' + numPoints + ']', expected, actual));
     }
 
     /**
@@ -484,12 +485,12 @@ search: for (int j=numPoints; --j >= 0;) {
             if (targets == null) {
                 tgtDim = tgt.getDimension();
                 if (tgtDim <= 0) {
-                    throw new MismatchedDimensionException(mismatchedDimension("target", 2, tgtDim));
+                    throw mismatchedDimension("target", 2, tgtDim);
                 }
                 if (gridSize == null) {
                     srcDim = src.getDimension();
                     if (srcDim <= 0) {
-                        throw new MismatchedDimensionException(mismatchedDimension("source", 2, srcDim));
+                        throw mismatchedDimension("source", 2, srcDim);
                     }
                     final int capacity = sourceToTarget.size();
                     sources = new double[srcDim][capacity];
@@ -505,8 +506,8 @@ search: for (int j=numPoints; --j >= 0;) {
              * we compute its index in the fixed-size target arrays.
              */
             int d;
-            if ((d = src.getDimension()) != srcDim) throw new MismatchedDimensionException(mismatchedDimension("source", srcDim, d));
-            if ((d = tgt.getDimension()) != tgtDim) throw new MismatchedDimensionException(mismatchedDimension("target", tgtDim, d));
+            if ((d = src.getDimension()) != srcDim) throw mismatchedDimension("source", srcDim, d);
+            if ((d = tgt.getDimension()) != tgtDim) throw mismatchedDimension("target", tgtDim, d);
             boolean isValid = true;
             int index;
             if (gridSize != null) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java b/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
index fb09345..ae1f680 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
@@ -80,7 +80,7 @@ import org.apache.sis.util.resources.Errors;
  * in the {@linkplain java.util.Locale#getDefault() default locale} if the check failed.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -491,7 +491,7 @@ public final class ArgumentChecks extends Static {
     public static void ensureBetween(final String name, final float min, final float max, final float value)
             throws IllegalArgumentException
     {
-        if (!(value >= min && value <= max)) { // Use '!' for catching NaN.
+        if (!(value >= min && value <= max)) {                              // Use '!' for catching NaN.
             throw new IllegalArgumentException(Float.isNaN(value) ?
                     Errors.format(Errors.Keys.NotANumber_1, name) :
                     Errors.format(Errors.Keys.ValueOutOfRange_4, name, min, max, value));
@@ -510,7 +510,7 @@ public final class ArgumentChecks extends Static {
     public static void ensureBetween(final String name, final double min, final double max, final double value)
             throws IllegalArgumentException
     {
-        if (!(value >= min && value <= max)) { // Use '!' for catching NaN.
+        if (!(value >= min && value <= max)) {                              // Use '!' for catching NaN.
             throw new IllegalArgumentException(Double.isNaN(value) ?
                     Errors.format(Errors.Keys.NotANumber_1, name)  :
                     Errors.format(Errors.Keys.ValueOutOfRange_4, name, min, max, value));
@@ -581,7 +581,7 @@ public final class ArgumentChecks extends Static {
     {
         if (crs != null) {
             final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs != null) { // Should never be null, but let be safe.
+            if (cs != null) {                                       // Should never be null, but let be safe.
                 final int dimension = cs.getDimension();
                 if (dimension != expected) {
                     throw new MismatchedDimensionException(Errors.format(
@@ -616,6 +616,30 @@ public final class ArgumentChecks extends Static {
     }
 
     /**
+     * Ensures that the given array of indices, if non-null, has the expected number of dimensions
+     * (taken as its length). This method does nothing if the given array is null.
+     *
+     * @param  name      the name of the argument to be checked. Used only if an exception is thrown.
+     * @param  expected  the expected number of dimensions.
+     * @param  indices   the array of indices to check for its number of dimensions, or {@code null}.
+     * @throws MismatchedDimensionException if the given array of indices is non-null and does not have
+     *         the expected number of dimensions (taken as its length).
+     *
+     * @since 1.0
+     */
+    public static void ensureDimensionMatches(final String name, final int expected, final int[] indices)
+            throws MismatchedDimensionException
+    {
+        if (indices != null) {
+            final int dimension = indices.length;
+            if (dimension != expected) {
+                throw new MismatchedDimensionException(Errors.format(
+                        Errors.Keys.MismatchedDimension_3, name, expected, dimension));
+            }
+        }
+    }
+
+    /**
      * Ensures that the given vector, if non-null, has the expected number of dimensions
      * (taken as its length). This method does nothing if the given vector is null.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index d8a9606..91067c6 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -1055,8 +1055,8 @@ public final class Errors extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -1070,7 +1070,7 @@ public final class Errors extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -1087,7 +1087,7 @@ public final class Errors extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
@@ -1106,7 +1106,7 @@ public final class Errors extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
index 3311d6e..b445f13 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
@@ -268,8 +268,8 @@ public final class Messages extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -283,7 +283,7 @@ public final class Messages extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -300,7 +300,7 @@ public final class Messages extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
@@ -319,7 +319,7 @@ public final class Messages extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
index 26a6e24..15d2346 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
@@ -165,8 +165,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -180,7 +180,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -197,7 +197,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
@@ -216,7 +216,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
index c63e18f..ff75602 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.storage.netcdf;
 
-import java.awt.Color;
 import java.util.List;
 import java.awt.image.DataBuffer;
 import java.awt.image.ColorModel;
@@ -71,7 +70,7 @@ final class Image extends GridCoverage {
         final int height = Math.toIntExact(extent.getSize(1));
         final WritableRaster raster = RasterFactory.createBandedRaster(data, width, height, width, null, null, null);
         final ColorModel colors = ColorModelFactory.createColorModel(getSampleDimensions(), VISIBLE_BAND, data.getDataType(),
-                (category) -> category.isQuantitative() ? new Color[] {Color.BLACK, Color.WHITE} : null);
+                ColorModelFactory.GRAYSCALE);
         return new BufferedImage(colors, raster, false, null);
     }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Resources.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Resources.java
index e2ef0c5..d40c6ee 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Resources.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Resources.java
@@ -139,8 +139,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -154,7 +154,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
index 366007f..b3d2cb6 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
@@ -367,8 +367,8 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}"
-     * with values of {@code arg0}.
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
      *
      * @param  key   the key for the desired string.
      * @param  arg0  value to substitute to "{0}".
@@ -382,7 +382,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}.
      *
      * @param  key   the key for the desired string.
@@ -399,7 +399,7 @@ public final class Resources extends IndexedResourceBundle {
     }
 
     /**
-     * Gets a string for the given key are replace all occurrence of "{0}",
+     * Gets a string for the given key and replaces all occurrence of "{0}",
      * "{1}", with values of {@code arg0}, {@code arg1}, etc.
      *
      * @param  key   the key for the desired string.


Mime
View raw message