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 edaef36  Prepare netCDF RasterResource to handle variables having 
bands.
edaef36 is described below

commit edaef369de17532c0f0dd3e39762f84300fb4c3c
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Mar 20 21:05:51 2019 +0100

    Prepare netCDF RasterResource to handle variables having bands.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   | 29 +++++++++-------
 .../apache/sis/coverage/grid/GridExtentTest.java   | 30 ++++++++++++----
 .../main/java/org/apache/sis/util/ArraysExt.java   | 30 ++++++++++------
 .../java/org/apache/sis/internal/netcdf/Axis.java  |  2 +-
 .../org/apache/sis/internal/netcdf/Convention.java |  7 ++--
 .../apache/sis/internal/netcdf/RasterResource.java | 40 +++++++++-------------
 .../org/apache/sis/internal/netcdf/Variable.java   | 28 ++++++++++++++-
 .../sis/internal/storage/AbstractGridResource.java | 26 ++++++++++++--
 .../org/apache/sis/internal/storage/Resources.java |  7 +++-
 .../sis/internal/storage/Resources.properties      |  5 +--
 .../sis/internal/storage/Resources_fr.properties   |  1 +
 .../apache/sis/storage/GridCoverageResource.java   |  7 ++--
 12 files changed, 145 insertions(+), 67 deletions(-)

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 722623c..04af16a 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
@@ -267,7 +267,7 @@ public class GridExtent implements Serializable {
      *
      * @see #getLow()
      * @see #getHigh()
-     * @see #append(DimensionNameType, long, long, boolean)
+     * @see #insert(int, DimensionNameType, long, long, boolean)
      */
     public GridExtent(final DimensionNameType[] axisTypes, final long[] low, 
final long[] high, final boolean isHighIncluded) {
         ArgumentChecks.ensureNonNull("high", high);
@@ -824,8 +824,10 @@ public class GridExtent implements Serializable {
     }
 
     /**
-     * Returns a new grid envelope with the specified dimension added after 
this grid envelope dimensions.
+     * Returns a new grid envelope with the specified dimension inserted at 
the given index in this grid envelope.
+     * To append a new dimension after all existing dimensions, set {@code 
offset} to {@link #getDimension()}.
      *
+     * @param  offset          where to insert the new dimension, from 0 to 
{@link #getDimension()} inclusive.
      * @param  axisType        the type of the grid axis to add, or {@code 
null} if unspecified.
      * @param  low             the valid minimum grid coordinate (always 
inclusive).
      * @param  high            the valid maximum grid coordinate, inclusive or 
exclusive depending on the next argument.
@@ -835,26 +837,29 @@ public class GridExtent implements Serializable {
      * @return a new grid envelope with the specified dimension added.
      * @throws IllegalArgumentException if the low coordinate value is greater 
than the high coordinate value.
      */
-    public GridExtent append(final DimensionNameType axisType, final long low, 
long high, final boolean isHighIncluded) {
+    public GridExtent insert(final int offset, final DimensionNameType 
axisType, final long low, long high, final boolean isHighIncluded) {
+        final int dimension = getDimension();
+        ArgumentChecks.ensureBetween("offset", 0, dimension, offset);
         if (!isHighIncluded) {
             high = Math.decrementExact(high);
         }
-        final int dimension = getDimension();
-        final int newDim    = dimension + 1;
+        final int newDim = dimension + 1;
         DimensionNameType[] axisTypes = null;
         if (types != null || axisType != null) {
             if (types != null) {
-                axisTypes = Arrays.copyOf(types, newDim);
+                axisTypes = ArraysExt.insert(types, offset, 1);
             } else {
                 axisTypes = new DimensionNameType[newDim];
             }
-            axisTypes[dimension] = axisType;
+            axisTypes[offset] = axisType;
         }
         final GridExtent ex = new GridExtent(newDim, axisTypes);
-        System.arraycopy(coordinates, 0,         ex.coordinates, 0,      
dimension);
-        System.arraycopy(coordinates, dimension, ex.coordinates, newDim, 
dimension);
-        ex.coordinates[dimension]          = low;
-        ex.coordinates[dimension + newDim] = high;
+        System.arraycopy(coordinates, 0,                  ex.coordinates, 0,   
                offset);
+        System.arraycopy(coordinates, offset,             ex.coordinates, 
offset + 1,          dimension - offset);
+        System.arraycopy(coordinates, dimension,          ex.coordinates, 
newDim,              offset);
+        System.arraycopy(coordinates, dimension + offset, ex.coordinates, 
newDim + offset + 1, dimension - offset);
+        ex.coordinates[offset]          = low;
+        ex.coordinates[offset + newDim] = high;
         ex.validateCoordinates();
         return ex;
     }
@@ -935,7 +940,7 @@ public class GridExtent implements Serializable {
      * The number of dimensions of the sub grid envelope will be {@code 
dimensions.length}.
      *
      * <p>This method performs a <cite>dimensionality reduction</cite> and can 
be used as the
-     * converse of {@link #append(DimensionNameType, long, long, boolean)}.
+     * converse of {@link #insert(int, DimensionNameType, long, long, 
boolean)}.
      * This method can not be used for changing dimension order.</p>
      *
      * @param  dimensions  the dimensions to select, in strictly increasing 
order.
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 8d63d52..6687c45 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
@@ -106,20 +106,36 @@ public final strictfp class GridExtentTest extends 
TestCase {
     }
 
     /**
-     * Tests {@link GridExtent#append(DimensionNameType, long, long, boolean)}.
+     * Tests {@link GridExtent#insert(int, DimensionNameType, long, long, 
boolean)}
+     * with {@code offset} set to {@link GridExtent#getDimension()}.
      */
     @Test
     public void testAppend() {
+        appendOrInsert(2, 1);
+    }
+
+    /**
+     * Tests {@link GridExtent#insert(int, DimensionNameType, long, long, 
boolean)}.
+     */
+    @Test
+    public void testInsert() {
+        appendOrInsert(1, 2);
+    }
+
+    /**
+     * Implementation of {@link #testAppend()} and {@link #testInsert()}.
+     */
+    private void appendOrInsert(final int offset, final int rowIndex) {
         GridExtent extent = new GridExtent(new DimensionNameType[] 
{DimensionNameType.COLUMN, DimensionNameType.ROW},
                                            new long[] {100, 200}, new long[] 
{500, 800}, true);
-        extent = extent.append(DimensionNameType.TIME, 40, 50, false);
+        extent = extent.insert(offset, DimensionNameType.TIME, 40, 50, false);
         assertEquals("dimension", 3, extent.getDimension());
-        assertExtentEquals(extent, 0, 100, 500);
-        assertExtentEquals(extent, 1, 200, 800);
-        assertExtentEquals(extent, 2,  40,  49);
+        assertExtentEquals(extent, 0,        100, 500);
+        assertExtentEquals(extent, rowIndex, 200, 800);
+        assertExtentEquals(extent, offset,    40,  49);
         assertEquals(DimensionNameType.COLUMN, extent.getAxisType(0).get());
-        assertEquals(DimensionNameType.ROW,    extent.getAxisType(1).get());
-        assertEquals(DimensionNameType.TIME,   extent.getAxisType(2).get());
+        assertEquals(DimensionNameType.ROW,    
extent.getAxisType(rowIndex).get());
+        assertEquals(DimensionNameType.TIME,   
extent.getAxisType(offset).get());
     }
 
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java 
b/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java
index 0bfb01f..063e217 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java
@@ -628,8 +628,9 @@ public final class ArraysExt extends Static {
      *
      * @param  <T>     the array type.
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -645,8 +646,9 @@ public final class ArraysExt extends Static {
             return array;               // May be null
         }
         ArgumentChecks.ensureNonNull ("array",  array);
-        ArgumentChecks.ensurePositive("length", length);
         final int arrayLength = Array.getLength(array);
+        ArgumentChecks.ensureBetween("first", 0, arrayLength, first);
+        ArgumentChecks.ensurePositive("length", length);
         @SuppressWarnings("unchecked")
         final T newArray = (T) 
Array.newInstance(array.getClass().getComponentType(), arrayLength + length);
         System.arraycopy(array, 0,     newArray, 0,            first           
 );
@@ -662,8 +664,9 @@ public final class ArraysExt extends Static {
      *
      * @param  <E>     the type of array elements.
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -687,8 +690,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -712,8 +716,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -737,8 +742,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -762,8 +768,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -812,8 +819,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -837,8 +845,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
@@ -862,8 +871,9 @@ public final class ArraysExt extends Static {
      * Otherwise this method creates a new array. In every cases, the given 
array is never modified.
      *
      * @param  array   array in which to insert spaces. Can be {@code null} 
only if {@code length} is 0.
-     * @param  first   index where the first space should be inserted. All 
{@code array} elements
+     * @param  first   index where the first space will be inserted. All 
{@code array} elements
      *                 having an index equal to or higher than {@code index} 
will be moved forward.
+     *                 Can be {@code array.length} for inserting spaces at the 
end of the array.
      * @param  length  number of spaces to insert.
      * @return array containing the {@code array} elements with the additional 
space inserted,
      *         or {@code array} (which may be null) if {@code length} is 0.
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index ca282a3..094f194 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -712,7 +712,7 @@ main:   switch (getDimension()) {
      * @throws DataStoreException if a logical error occurred.
      */
     final Vector read() throws IOException, DataStoreException {
-        final TransferFunction tr = 
coordinates.decoder.convention().transferFunction(coordinates);
+        final TransferFunction tr = coordinates.getTransferFunction();
         if (TransferFunctionType.LINEAR.equals(tr.getType())) {
             Vector data = coordinates.read();
             data = data.subList(0, getSizeProduct(0));                  // 
Trim trailing NaN values.
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index 0dfac3b..d5f7625 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -195,13 +195,10 @@ public class Convention {
      *       to confuse them with images.</li>
      * </ul>
      *
-     * @param  variable  the variable for which to get the role, or {@code 
null}.
-     * @return role of the given variable, or {@code null} if the given 
variable was null.
+     * @param  variable  the variable for which to get the role.
+     * @return role of the given variable.
      */
     public VariableRole roleOf(final Variable variable) {
-        if (variable == null) {
-            return null;
-        }
         if (variable.isCoordinateSystemAxis()) {
             return VariableRole.AXIS;
         }
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
index fbbd463..dfeadf5 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
@@ -109,10 +109,17 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
 
     /**
      * The netCDF variable wrapped by this resource.
+     * The same variable may be repeated if one of its dimension shall be 
interpreted as bands.
      */
     private final Variable[] data;
 
     /**
+     * If one of {@link #data} dimension provides values for different bands, 
that dimension index.
+     * Otherwise -1.
+     */
+    private final int bandDimension;
+
+    /**
      * Path to the netCDF file for information purpose, or {@code null} if 
unknown.
      *
      * @see #getComponentFiles()
@@ -120,12 +127,6 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
     private final Path location;
 
     /**
-     * Allows to fetch referencing information from source variables. It's 
mostly useful for handling netCDF files
-     * structured in ways different than CF-convention (for example GCOM, 
<i>etc.</i>).
-     */
-    private final Convention convention;
-
-    /**
      * Creates a new resource. All variables in the {@code data} list shall 
have the same domain and the same grid geometry.
      *
      * @param  decoder  the implementation used for decoding the netCDF file.
@@ -141,8 +142,8 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
         ranges       = new SampleDimension[data.length];
         identifier   = decoder.nameFactory.createLocalName(decoder.namespace, 
name);
         location     = decoder.location;
-        convention   = decoder.convention();
         gridGeometry = grid;
+        bandDimension = -1;
     }
 
     /**
@@ -159,7 +160,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
         final List<Resource> resources = new ArrayList<>();
         for (int i=0; i<variables.length; i++) {
             final Variable variable = variables[i];
-            if (decoder.convention().roleOf(variable) != 
VariableRole.COVERAGE) {                 // Variable may be null.
+            if (variable == null || variable.getRole() != 
VariableRole.COVERAGE) {
                 continue;                                                   // 
Skip variables that are not grid coverages.
             }
             final GridGeometry grid = variable.getGridGeometry();
@@ -187,7 +188,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
                     int suffixLength = name.length() - suffixStart;
                     for (int j=i; ++j < variables.length;) {
                         final Variable candidate = variables[j];
-                        if (decoder.convention().roleOf(candidate) != 
VariableRole.COVERAGE) {        // Variable may be null.
+                        if (candidate == null || candidate.getRole() != 
VariableRole.COVERAGE) {
                             variables[j] = null;                               
 // For avoiding to revisit that variable again.
                             continue;
                         }
@@ -287,12 +288,9 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
          * is used only as a fallback. We give precedence to the range 
computed by Apache SIS instead than the range given
          * by UCAR because we need the range of packed values instead than the 
range of converted values.
          */
-        NumberRange<?> range = convention.validRange(band);
-        if (range == null) {
-            range = band.getRangeFallback();                                // 
Fallback to UCAR library may happen here.
-        }
+        NumberRange<?> range = band.getValidRange();
         if (range != null) {
-            final MathTransform1D mt = 
convention.transferFunction(band).getTransform();
+            final MathTransform1D mt = 
band.getTransferFunction().getTransform();
             if (!mt.isIdentity() && range instanceof MeasurementRange<?>) {
                 /*
                  * Heuristic rule defined in UCAR documentation (see 
EnhanceScaleMissing interface):
@@ -382,7 +380,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
      */
     @Override
     public GridCoverage read(GridGeometry domain, int... range) throws 
DataStoreException {
-        range = validateRangeArgument(data.length, range);
+        range = validateRangeArgument(ranges.length, range);
         if (domain == null) {
             domain = gridGeometry;
         }
@@ -405,19 +403,15 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
             /*
              * Iterate over netCDF variables in the order they appear in the 
file, not in the order requested
              * by the 'range' argument.  The intent is to perform sequential 
I/O as much as possible, without
-             * seeking backward.
+             * seeking backward. A side effect is that even if the same sample 
dimension were requested many
+             * times, the data would still be loaded at most once.
              */
             for (int i=0; i<data.length; i++) {
                 final Variable variable = data[i];
                 SampleDimension def = ranges[i];
                 Buffer values = null;
                 for (int j=0; j<range.length; j++) {
-                    /*
-                     * Check if the current variable is a sample dimension 
specified in the 'range' argument.
-                     * Note that the same sample dimension may be requested an 
arbitrary amount of time, but
-                     * the data will be loaded at most once.
-                     */
-                    if (range[j] == i) {
+                    if (range[j] == i) {                // Search sample 
dimensions specified in the 'range' argument.
                         if (def == null) {
                             if (builder == null) builder = new 
SampleDimension.Builder();
                             ranges[i] = def = createSampleDimension(builder, 
variable);
@@ -448,7 +442,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
         if (imageBuffer == null) {
             throw new 
DataStoreContentException(Errors.format(Errors.Keys.UnsupportedType_1, 
dataType.name()));
         }
-        return new Raster(domain, UnmodifiableArrayList.wrap(selected), 
imageBuffer, first.getName());
+        return new Raster(domain, UnmodifiableArrayList.wrap(selected), 
imageBuffer, first.getStandardName());
     }
 
     /**
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
index 2d328cc..e28037b 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
@@ -29,6 +29,7 @@ import java.io.IOException;
 import java.time.Instant;
 import javax.measure.Unit;
 import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.referencing.operation.transform.TransferFunction;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.InternalDataStoreException;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -149,7 +150,7 @@ public abstract class Variable extends NamedElement {
     }
 
     /**
-     * Returns the name of this variable.
+     * Returns the name of this variable. May be used as sample dimension name 
in a raster.
      *
      * @return the name of this variable.
      */
@@ -158,6 +159,7 @@ public abstract class Variable extends NamedElement {
 
     /**
      * Returns the standard name if available, or the long name other, or the 
ordinary name otherwise.
+     * May be used as the label of a {@link Raster} as a whole (including all 
bands).
      *
      * @return the standard name, or a fallback if there is no standard name.
      */
@@ -174,6 +176,7 @@ public abstract class Variable extends NamedElement {
 
     /**
      * Returns the description of this variable, or {@code null} if none.
+     * May be used as a category name in a sample dimension of a {@link 
Raster}.
      * This information may be encoded in different attributes like {@code 
"description"}, {@code "title"},
      * {@code "long_name"} or {@code "standard_name"}. If the return value is 
non-null, then it should also
      * be non-empty.
@@ -832,6 +835,22 @@ public abstract class Variable extends NamedElement {
     }
 
     /**
+     * Returns the range of valid values, or {@code null} if unknown. This is 
a shortcut for
+     * {@link Convention#validRange(Variable)} with a fallback on {@link 
#getRangeFallback()}.
+     *
+     * @return the range of valid values, or {@code null} if unknown.
+     *
+     * @see Convention#validRange(Variable)
+     */
+    final NumberRange<?> getValidRange() {
+        NumberRange<?> range = decoder.convention().validRange(this);
+        if (range == null) {
+            range = getRangeFallback();
+        }
+        return range;
+    }
+
+    /**
      * Returns the range of values as determined by the data type or other 
means, or {@code null} if unknown.
      * This method is invoked only as a fallback if {@link 
Convention#validRange(Variable)} did not found a
      * range of values by application of CF conventions. The returned range 
may be a range of packed values
@@ -908,6 +927,13 @@ public abstract class Variable extends NamedElement {
     }
 
     /**
+     * Builds the function converting values from their packed formats in the 
variable to "real" values.
+     */
+    final TransferFunction getTransferFunction() {
+        return decoder.convention().transferFunction(this);
+    }
+
+    /**
      * Reads all the data for this variable and returns them as an array of a 
Java primitive type.
      * Multi-dimensional variables are flattened as a one-dimensional array 
(wrapped in a vector).
      * Example:
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
index 9d5cd02..1355db9 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.storage;
 
+import java.util.BitSet;
 import org.opengis.geometry.Envelope;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
@@ -91,8 +92,12 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
 
     /**
      * Validate the {@code range} argument given to {@link #read(GridGeometry, 
int...)}.
-     * This method verifies that all indices are between 0 and {@code 
numSampleDimensions},
-     * but does not verify if there is duplicated indices since such 
duplication is allowed.
+     * This method verifies that all indices are between 0 and {@code 
numSampleDimensions}
+     * and that there is no duplicated index.
+     *
+     * <p>On success, this method always returns a non-null array with the 
same content than the user argument.
+     * The user specified array is not returned directly in order to allow the 
caller to perform modifications,
+     * and also as a paranoiac safety against concurrent changes.</p>
      *
      * @param  numSampleDimensions  number of sample dimensions.
      * @param  range  the {@code range} argument given by the user. May be 
null or empty.
@@ -105,12 +110,29 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
             return ArraysExt.range(0, numSampleDimensions);
         }
         range = range.clone();
+        long previous = 0;                          // Cheap way to remember 
previous dimensions.
+        BitSet extra = null;                        // Used only if there is 
more than 64 dimensions.
         for (int i=0; i<range.length; i++) {
             final int r = range[i];
             if (r < 0 || r >= numSampleDimensions) {
                 throw new 
IllegalArgumentException(Resources.forLocale(getLocale()).getString(
                         Resources.Keys.InvalidSampleDimensionIndex_2, i, 
numSampleDimensions - 1));
             }
+            final boolean duplicated;
+            if (r < Long.SIZE) {
+                duplicated = (previous == (previous |= (1L << r)));
+            } else {
+                if (extra == null) {
+                    extra = new BitSet();
+                }
+                final int j = r - Long.SIZE;
+                duplicated = extra.get(j);
+                extra.set(j);
+            }
+            if (duplicated) {
+                throw new 
IllegalArgumentException(Resources.forLocale(getLocale()).getString(
+                        Resources.Keys.DuplicatedSampleDimensionIndex_1, i));
+            }
         }
         return range;
     }
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 b3d2cb6..1fa0c5e 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
@@ -142,7 +142,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short DataStoreEncoding = 29;
 
         /**
-         * Formating conventions of dates and numbers.
+         * Formatting conventions of dates and numbers.
          */
         public static final short DataStoreLocale = 30;
 
@@ -167,6 +167,11 @@ public final class Resources extends IndexedResourceBundle 
{
         public static final short DirectoryContent_1 = 35;
 
         /**
+         * Sample dimension index {0} is duplicated.
+         */
+        public static final short DuplicatedSampleDimensionIndex_1 = 53;
+
+        /**
          * Character string in the “{0}” file is too long. The string has {2} 
characters while the
          * limit is {1}.
          */
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
index 616f8e9..3730a42 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
@@ -35,17 +35,18 @@ ConcurrentRead_1                  = One or more read 
operations are in progress
 ConcurrentWrite_1                 = A write operation is in progress in the 
\u201c{0}\u201d data store.
 DataStoreCreate                   = Whether to allow new data store creation 
if the source to open does not already exist.
 DataStoreEncoding                 = Character encoding used by the data store.
-DataStoreLocale                   = Formating conventions of dates and numbers.
+DataStoreLocale                   = Formatting conventions of dates and 
numbers.
 DataStoreLocation                 = Data store location as a file or URL.
 DataStoreTimeZone                 = Timezone of dates in the data store.
 DirectoryContent_1                = Content of \u201c{0}\u201d directory.
 DirectoryContentFormatName        = Name of the format to use for reading or 
writing the directory content.
+DuplicatedSampleDimensionIndex_1  = Sample dimension index {0} is duplicated.
+ExcessiveStringSize_3             = Character string in the \u201c{0}\u201d 
file is too long. The string has {2} characters while the limit is {1}.
 FeatureAlreadyPresent_2           = A feature named \u201c{1}\u201d is already 
present in the \u201c{0}\u201d data store.
 FeatureNotFound_2                 = Feature \u201c{1}\u201d has not been found 
in the \u201c{0}\u201d data store.
 FileAlreadyExists_2               = A {1,choice,0#file|1#directory} already 
exists at \u201c{0}\u201d.
 FileIsNotAResourceDirectory_1     = The \u201c{0}\u201d file is not a 
directory of resources.
 FoliationRepresentation           = Whether to assemble trajectory fragments 
(lines in CSV file) in a single feature instance.
-ExcessiveStringSize_3             = Character string in the \u201c{0}\u201d 
file is too long. The string has {2} characters while the limit is {1}.
 IllegalFeatureType_2              = The {0} data store does not accept 
features of type \u201c{1}\u201d.
 IllegalInputTypeForReader_2       = The {0} reader does not accept inputs of 
type \u2018{1}\u2019.
 IllegalOutputTypeForWriter_2      = The {0} writer does not accept outputs of 
type \u2018{1}\u2019.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
index f5f3739..f87a2d8 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
@@ -45,6 +45,7 @@ DataStoreLocation                 = Chemin (fichier ou URL) 
vers la source de do
 DataStoreTimeZone                 = Fuseau horaire des dates dans les 
donn\u00e9es.
 DirectoryContent_1                = Contenu du r\u00e9pertoire 
\u00ab\u202f{0}\u202f\u00bb.
 DirectoryContentFormatName        = Nom du format ou de la source de 
donn\u00e9es \u00e0 utiliser pour lire ou \u00e9crire le contenu du 
r\u00e9pertoire.
+DuplicatedSampleDimensionIndex_1  = L\u2019index de dimension 
d\u2019\u00e9chantillonnage {0} est r\u00e9p\u00e9t\u00e9.
 ExcessiveStringSize_3             = La cha\u00eene de caract\u00e8res dans le 
fichier \u00ab\u202f{0}\u202f\u00bb est trop longue. La cha\u00eene fait {2} 
caract\u00e8res alors que la limite est {1}.
 FeatureAlreadyPresent_2           = Une entit\u00e9 nomm\u00e9e 
\u00ab\u202f{1}\u202f\u00bb est d\u00e9j\u00e0 pr\u00e9sente dans les 
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
 FeatureNotFound_2                 = L\u2019entit\u00e9 
\u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans les 
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
index fc632bd..b9f273b 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
@@ -90,9 +90,10 @@ public interface GridCoverageResource extends DataSet {
      * <i>etc</i>. The general contract is that the returned coverage should 
not contain less data than a coverage
      * matching exactly the given geometry.
      *
-     * <p>The returned coverage shall contain the exact set of sample 
dimensions specified by the {@code range}
-     * argument, in the specified order. The "best-effort basis" flexibility 
applies only to the grid geometry,
-     * not to the range.</p>
+     * <p>The returned coverage shall contain the exact set of sample 
dimensions specified by the {@code range} argument,
+     * in the specified order (the "best-effort basis" flexibility applies 
only to the grid geometry, not to the range).
+     * All {@code range} values shall be between 0 inclusive and 
<code>{@linkplain #getSampleDimensions()}.size()</code>
+     * exclusive, without duplicated values.</p>
      *
      * <p>While this method name suggests an immediate reading, some 
implementations may defer the actual reading
      * at a later stage.</p>

Reply via email to