sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/03: If a netCDF file contains two variables that are components of wind vectors or current vectors, they should appear as sample dimensions of the same grid coverage.
Date Thu, 20 Dec 2018 15:26:57 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit c2065de750e431367b4be0914570ca05ebf1c9f2
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Dec 20 15:52:06 2018 +0100

    If a netCDF file contains two variables that are components of wind vectors or current
vectors, they should appear as sample dimensions of the same grid coverage.
---
 .../apache/sis/internal/raster/RasterFactory.java  |  59 +++++
 .../java/org/apache/sis/math/SequenceVector.java   |  64 ++++-
 .../src/main/java/org/apache/sis/math/Vector.java  |   6 +-
 .../org/apache/sis/internal/netcdf/DataType.java   |  33 ---
 .../org/apache/sis/internal/netcdf/Variable.java   |  17 ++
 .../apache/sis/storage/netcdf/GridResource.java    | 292 +++++++++++++++------
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |  13 +-
 7 files changed, 348 insertions(+), 136 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
index 15e7f08..002ec85 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
@@ -16,13 +16,22 @@
  */
 package org.apache.sis.internal.raster;
 
+import java.nio.Buffer;
+import java.nio.ReadOnlyBufferException;
 import java.awt.Point;
 import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferShort;
+import java.awt.image.DataBufferUShort;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferDouble;
 import java.awt.image.SampleModel;
 import java.awt.image.BandedSampleModel;
 import java.awt.image.WritableRaster;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -78,4 +87,54 @@ public final class RasterFactory extends Static {
             }
         }
     }
+
+    /**
+     * Wraps the backing arrays of given NIO buffers into Java2D buffers.
+     * This method wraps the underlying array of primitive types; data are not copied.
+     * For each buffer, the data starts at {@linkplain Buffer#position() buffer position}
+     * and ends at the position + {@linkplain Buffer#remaining() remaining}.
+     *
+     * @param  dataType  type of buffer to create as one of {@link DataBuffer} constants.
+     * @param  data      the data, one for each band.
+     * @return buffer of the given type, or {@code null} if {@code dataType} is unrecognized.
+     * @throws UnsupportedOperationException if a buffer is not backed by an accessible array.
+     * @throws ReadOnlyBufferException if a buffer is backed by an array but is read-only.
+     * @throws ArrayStoreException if the type of a backing array is not {@code dataType}.
+     * @throws ArithmeticException if the position of a buffer is too high.
+     * @throws IllegalArgumentException if buffers do not have the same amount of remaining
values.
+     */
+    public static DataBuffer wrap(final int dataType, final Buffer... data) {
+        final int numBands = data.length;
+        final Object[] arrays;
+        switch (dataType) {
+            case DataBuffer.TYPE_USHORT: // fall through
+            case DataBuffer.TYPE_SHORT:  arrays = new short [numBands][]; break;
+            case DataBuffer.TYPE_INT:    arrays = new int   [numBands][]; break;
+            case DataBuffer.TYPE_BYTE:   arrays = new byte  [numBands][]; break;
+            case DataBuffer.TYPE_FLOAT:  arrays = new float [numBands][]; break;
+            case DataBuffer.TYPE_DOUBLE: arrays = new double[numBands][]; break;
+            default: return null;
+        }
+        final int[] offsets = new int[numBands];
+        int length = 0;
+        for (int i=0; i<numBands; i++) {
+            final Buffer buffer = data[i];
+            arrays [i] = buffer.array();
+            offsets[i] = Math.addExact(buffer.arrayOffset(), buffer.position());
+            final int r = buffer.remaining();
+            if (i == 0) length = r;
+            else if (length != r) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLengths));
+            }
+        }
+        switch (dataType) {
+            case DataBuffer.TYPE_BYTE:   return new DataBufferByte  (  (byte[][]) arrays,
length, offsets);
+            case DataBuffer.TYPE_SHORT:  return new DataBufferShort ( (short[][]) arrays,
length, offsets);
+            case DataBuffer.TYPE_USHORT: return new DataBufferUShort( (short[][]) arrays,
length, offsets);
+            case DataBuffer.TYPE_INT:    return new DataBufferInt   (   (int[][]) arrays,
length, offsets);
+            case DataBuffer.TYPE_FLOAT:  return new DataBufferFloat ( (float[][]) arrays,
length, offsets);
+            case DataBuffer.TYPE_DOUBLE: return new DataBufferDouble((double[][]) arrays,
length, offsets);
+            default: return null;
+        }
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java b/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java
index 9613a77..c707a04 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/SequenceVector.java
@@ -132,7 +132,7 @@ abstract class SequenceVector extends Vector implements Serializable {
     /**
      * A vector which is a sequence of increasing or decreasing {@code double} values.
      */
-    static final class Doubles extends SequenceVector {
+    static class Doubles extends SequenceVector {
         /**
          * For cross-version compatibility.
          */
@@ -147,7 +147,7 @@ abstract class SequenceVector extends Vector implements Serializable {
          * The difference between the values at two adjacent indexes.
          * May be positive, negative or zero.
          */
-        private final double increment;
+        final double increment;
 
         /**
          * Creates a sequence of numbers in a given range of values using the given increment.
@@ -169,26 +169,26 @@ abstract class SequenceVector extends Vector implements Serializable
{
         }
 
         /** Returns {@code true} if this vector contains only integer values. */
-        @Override public boolean isInteger() {
+        @Override public final boolean isInteger() {
             return Math.floor(first) == first && Math.floor(increment) == increment;
         }
 
         /**
          * Returns {@code true} if this vector returns {@code NaN} values.
          */
-        @Override public boolean isNaN(final int index) {
+        @Override public final boolean isNaN(final int index) {
             return Double.isNaN(first) || Double.isNaN(increment);
         }
 
         /** Computes the value at the given index. */
-        @Override public double doubleValue(final int index) {
+        @Override public final double doubleValue(final int index) {
             ArgumentChecks.ensureValidIndex(length, index);
             return first + increment*index;
             // TODO: use Math.fma with JDK9.
         }
 
         /** Computes the value at the given index. */
-        @Override public float floatValue(final int index) {
+        @Override public final float floatValue(final int index) {
             return (float) doubleValue(index);
         }
 
@@ -209,15 +209,53 @@ abstract class SequenceVector extends Vector implements Serializable
{
 
         /** Computes the minimal and maximal values in this vector. */
         @SuppressWarnings({"unchecked","rawtypes"})
-        @Override public NumberRange<?> range() {
-            // TODO: use Math.fma with JDK9.
-            double min = first;
-            double max = first + increment * (length - 1);
-            if (max < min) {
+        @Override public final NumberRange<?> range() {
+            Number min = get(0);
+            Number max = get(length - 1);
+            if (((Comparable) max).compareTo((Comparable) min) < 0) {
+                Number tmp = min;
                 min = max;
-                max = first;
+                max = tmp;
             }
-            return new NumberRange(type, Numbers.wrap(min, type), true, Numbers.wrap(max,
type), true);
+            return new NumberRange(type, min, true, max, true);
+        }
+    }
+
+
+    /**
+     * A vector which is a sequence of increasing or decreasing {@code float} values.
+     */
+    static final class Floats extends Doubles {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 7972249253456554448L;
+
+        /**
+         * Creates a sequence of numbers in a given range of values using the given increment.
+         */
+        Floats(final Class<? extends Number> type, final Number first, final Number
increment, final int length) {
+            super(type, first, increment, length);
+        }
+
+        /** Creates a new sequence for a subrange of this vector. */
+        @Override Vector createSubSampling(final int offset, final int step, final int n)
{
+            return new Floats(type, doubleValue(offset), increment*step, n);
+        }
+
+        /** Computes the value at the given index. */
+        @Override public Number get(final int index) {
+            return floatValue(index);
+        }
+
+        /** Returns the string representation of the value at the given index. */
+        @Override public String stringValue(final int index) {
+            return String.valueOf(floatValue(index));
+        }
+
+        /** Returns the increment between all consecutive values */
+        @Override public Number increment(final double tolerance) {
+            return (float) increment;
         }
     }
 
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
index dcf9fac..90b1fe4 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/Vector.java
@@ -201,6 +201,8 @@ public abstract class Vector extends AbstractList<Number> implements
RandomAcces
         if (t >= Numbers.BYTE && t <= Numbers.LONG) {
             // Use the long type if possible because not all long values can be represented
as double.
             return new SequenceVector.Longs(type, first, increment, length);
+        } else if (t == Numbers.FLOAT) {
+            return new SequenceVector.Floats(type, first, increment, length);
         } else {
             return new SequenceVector.Doubles(type, first, increment, length);
         }
@@ -454,7 +456,7 @@ public abstract class Vector extends AbstractList<Number> implements
RandomAcces
      * <div class="note"><b>Example:</b>
      * if {@link #getElementType()} returns {@code Byte.class} but {@link #isUnsigned()}
returns {@code true},
      * then this method may return instances of {@link Short} since that type is the smallest
Java primitive
-     * type capable to hold byte values in the [128 … 255] range.</div>
+     * type capable to hold byte values in the [0 … 255] range.</div>
      *
      * @param  index  the index in the [0 … {@linkplain #size() size}-1] range.
      * @return the value at the given index (may be {@code null}).
@@ -1231,7 +1233,7 @@ search:     for (;;) {
         int i = 0;
         do if (i >= length) {
             final Double NaN = Numerics.valueOf(Double.NaN);
-            return new SequenceVector.Doubles(getElementType(), NaN, NaN, length);
+            return createSequence(getElementType(), NaN, NaN, length);
         } while (isNaN(i++));
         /*
          * Verify if the vector contains repetitions. If yes, then we can keep only a subregion
of this vector.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
index 8b6a45b..b86a8cc 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
@@ -16,17 +16,8 @@
  */
 package org.apache.sis.internal.netcdf;
 
-import java.nio.Buffer;
 import java.awt.image.DataBuffer;
-import java.awt.image.DataBufferByte;
-import java.awt.image.DataBufferShort;
-import java.awt.image.DataBufferUShort;
-import java.awt.image.DataBufferInt;
-import java.awt.image.DataBufferFloat;
-import java.awt.image.DataBufferDouble;
 import org.apache.sis.util.Numbers;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.storage.DataStoreContentException;
 
 
 /**
@@ -199,30 +190,6 @@ public enum DataType {
     }
 
     /**
-     * Creates a Java2D buffer for the given {@code java.nio} buffer.
-     *
-     * @param  data  an array of primitive type such as {@code byte[]} or {@code float[]}.
-     * @return Java2D data buffer wrapping the given primitive array.
-     * @throws UnsupportedOperationException if the buffer is not backed by an array.
-     * @throws DataStoreContentException if this enumeration is not a supported type for
this operation.
-     * @throws ClassCastException if the given array is not of the type expected by this
enumeration value.
-     */
-    public DataBuffer toJava2D(final Buffer data) throws DataStoreContentException {
-        final Object array = data.array();
-        final int offset = data.arrayOffset() + data.position();
-        final int length = data.remaining();
-        switch (rasterDataType) {
-            case DataBuffer.TYPE_BYTE:   return new DataBufferByte  (  (byte[]) array, length,
offset);
-            case DataBuffer.TYPE_SHORT:  return new DataBufferShort ( (short[]) array, length,
offset);
-            case DataBuffer.TYPE_USHORT: return new DataBufferUShort( (short[]) array, length,
offset);
-            case DataBuffer.TYPE_INT:    return new DataBufferInt   (   (int[]) array, length,
offset);
-            case DataBuffer.TYPE_FLOAT:  return new DataBufferFloat ( (float[]) array, length,
offset);
-            case DataBuffer.TYPE_DOUBLE: return new DataBufferDouble((double[]) array, length,
offset);
-            default: throw new DataStoreContentException(Errors.format(Errors.Keys.UnsupportedType_1,
name()));
-        }
-    }
-
-    /**
      * An array of all supported netCDF data types ordered in such a way that
      * {@code VALUES[codeNetCDF - 1]} is the enumeration value for a given netCDF code.
      */
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 e19ff21..a440652 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
@@ -35,6 +35,7 @@ import org.apache.sis.util.Numbers;
 import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.util.resources.Errors;
 import ucar.nc2.constants.CDM;                      // We use only String constants.
+import ucar.nc2.constants.CF;
 
 
 /**
@@ -131,6 +132,22 @@ public abstract class Variable extends NamedElement {
     public abstract String getName();
 
     /**
+     * Returns the standard name if available, or the long name other, or the ordinary name
otherwise.
+     *
+     * @return the standard name, or a fallback if there is no standard name.
+     */
+    public final String getStandardName() {
+        String name = getAttributeAsString(CF.STANDARD_NAME);
+        if (name == null) {
+            name = getAttributeAsString(CDM.LONG_NAME);
+            if (name == null) {
+                name = getName();
+            }
+        }
+        return name;
+    }
+
+    /**
      * Returns the description of this variable, or {@code null} if none.
      *
      * @return the description of this variable, or {@code null}.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
index 0acde9c..1e803cb 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
@@ -18,9 +18,11 @@ package org.apache.sis.storage.netcdf;
 
 import java.util.Map;
 import java.util.List;
-import java.util.Collections;
+import java.util.ArrayList;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.nio.Buffer;
+import java.awt.image.DataBuffer;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.operation.MathTransform1D;
@@ -29,18 +31,23 @@ import org.apache.sis.referencing.operation.transform.TransferFunction;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Grid;
+import org.apache.sis.internal.netcdf.DataType;
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.storage.AbstractGridResource;
 import org.apache.sis.internal.storage.ResourceOnFileSystem;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridChange;
 import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.internal.raster.RasterFactory;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.storage.Resource;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.Numbers;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.math.Vector;
-import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
 import ucar.nc2.constants.CDM;                      // We use only String constants.
 
 
@@ -56,6 +63,26 @@ import ucar.nc2.constants.CDM;                      // We use only String
consta
  */
 final class GridResource extends AbstractGridResource implements ResourceOnFileSystem {
     /**
+     * Words used in standard (preferred) or long (if no standard) variable names which suggest
+     * that the variable is a component of a vector. Example of standard variable names:
+     *
+     * <ul>
+     *   <li>{@code baroclinic_eastward_sea_water_velocity}</li>
+     *   <li>{@code baroclinic_northward_sea_water_velocity}</li>
+     *   <li>{@code eastward_atmosphere_water_transport_across_unit_distance}</li>
+     *   <li><i>etc.</i></li>
+     * </ul>
+     *
+     * One element to note is that direction (e.g. "eastward") is not necessarily at the
beginning
+     * of variable name.
+     *
+     * @see <a href="http://cfconventions.org/Data/cf-standard-names/current/build/cf-standard-name-table.html">Standard
name table</a>
+     */
+    private static final String[] VECTOR_COMPONENT_NAMES = {
+        "eastward", "westward", "northward", "southward", "upward", "downward"
+    };
+
+    /**
      * The identifier of this grid resource. This is the variable name.
      *
      * @see #getIdentifier()
@@ -72,14 +99,14 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
     /**
      * The netCDF variable wrapped by this resource.
      */
-    private final Variable data;
+    private final Variable[] data;
 
     /**
-     * The sample dimension for the {@link #data} variable, created when first needed.
+     * The sample dimension for the {@link #data} variables, created when first needed.
      *
      * @see #getSampleDimensions()
      */
-    private SampleDimension definition;
+    private List<SampleDimension> definitions;
 
     /**
      * Path to the netCDF file for information purpose, or {@code null} if unknown.
@@ -92,18 +119,102 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
      * Creates a new resource.
      *
      * @param  decoder  the implementation used for decoding the netCDF file.
+     * @param  name     the name for the resource.
      * @param  grid     the grid geometry (size, CRS…) of the {@linkplain #data} cube.
-     * @param  data     the variable providing actual data.
+     * @param  data     the variables providing actual data. Shall contain at least one variable.
      */
-    GridResource(final Decoder decoder, final Grid grid, final Variable data) throws IOException,
DataStoreException {
+    private GridResource(final Decoder decoder, final String name, final Grid grid, final
List<Variable> data)
+            throws IOException, DataStoreException
+    {
         super(decoder.listeners);
-        this.data    = data;
+        this.data    = data.toArray(new Variable[data.size()]);
         gridGeometry = grid.getGridGeometry(decoder);
-        identifier   = decoder.nameFactory.createLocalName(decoder.namespace, data.getName());
+        identifier   = decoder.nameFactory.createLocalName(decoder.namespace, name);
         location     = decoder.location;
     }
 
     /**
+     * Creates all grid resources from the given decoder.
+     *
+     * @param  decoder  the implementation used for decoding the netCDF file.
+     */
+    static List<Resource> create(final Decoder decoder) throws IOException, DataStoreException
{
+        final Variable[]     variables = decoder.getVariables().clone();        // Needs
a clone because may be modified.
+        final List<Variable> siblings  = new ArrayList<>(4);
+        final List<Resource> resources = new ArrayList<>();
+        for (int i=0; i<variables.length; i++) {
+            final Variable variable = variables[i];
+            final Grid grid;
+            if (variable == null || !variable.isCoverage() || (grid = variable.getGridGeometry(decoder))
== null) {
+                continue;                                                   // Skip variables
that are not grid coverages.
+            }
+            siblings.add(variable);
+            String name = variable.getStandardName();
+            final DataType type = variable.getDataType();
+            /*
+             * At this point we found a variable for which to create a resource. Most of
the time, there is nothing else to do;
+             * the resource will have a single variable and the same name than that unique
variable. However in some cases, the
+             * we should put other variables together with the one we just found. Example:
+             *
+             *    1) baroclinic_eastward_sea_water_velocity
+             *    2) baroclinic_northward_sea_water_velocity
+             *
+             * We use the "eastward" and "northward" keywords for recognizing such pairs,
providing that everything else in the
+             * name is the same and the grid geometry are the same.
+             */
+            for (final String keyword : VECTOR_COMPONENT_NAMES) {
+                final int prefixLength = name.indexOf(keyword);
+                if (prefixLength >= 0) {
+                    int suffixStart  = prefixLength + keyword.length();
+                    int suffixLength = name.length() - suffixStart;
+                    for (int j=i; ++j < variables.length;) {
+                        final Variable candidate = variables[j];
+                        if (candidate == null || !candidate.isCoverage()) {
+                            variables[j] = null;                                // For avoiding
to revisit that variable again.
+                            continue;
+                        }
+                        final String cn = candidate.getStandardName();
+                        if (cn.regionMatches(cn.length() - suffixLength, name, suffixStart,
suffixLength) &&
+                            cn.regionMatches(0, name, 0, prefixLength) && candidate.getDataType()
== type &&
+                            candidate.getGridGeometry(decoder) == grid)
+                        {
+                            /*
+                             * Found another variable with the same name except for the keyword.
Verify that the
+                             * keyword is replaced by another word in the vector component
keyword list. If this
+                             * is the case, then we consider that those two variables should
be kept together.
+                             */
+                            for (final String k : VECTOR_COMPONENT_NAMES) {
+                                if (cn.regionMatches(prefixLength, k, 0, k.length())) {
+                                    siblings.add(candidate);
+                                    variables[j] = null;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    /*
+                     * If we have more than one variable, omit the keyword from the name.
For example instead
+                     * of "baroclinic_eastward_sea_water_velocity", construct "baroclinic_sea_water_velocity".
+                     * Note that we may need to remove duplicated '_' character after keyword
removal.
+                     */
+                    if (siblings.size() > 1) {
+                        if (suffixLength != 0) {
+                            final int c = name.codePointAt(suffixStart);
+                            if ((prefixLength != 0) ? (c == name.codePointBefore(prefixLength))
: (c == '_')) {
+                                suffixStart += Character.charCount(c);
+                            }
+                        }
+                        name = new StringBuilder(name).delete(prefixLength, suffixStart).toString();
+                    }
+                }
+            }
+            resources.add(new GridResource(decoder, name.trim(), grid, siblings));
+            siblings.clear();
+        }
+        return resources;
+    }
+
+    /**
      * Returns the variable name as an identifier of this resource.
      */
     @Override
@@ -123,79 +234,95 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
      * Returns the ranges of sample values together with the conversion from samples to real
values.
      */
     @Override
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public List<SampleDimension> getSampleDimensions() {
-        if (definition == null) {
+        if (definitions == null) {
             final SampleDimension.Builder builder = new SampleDimension.Builder();
-            NumberRange<?> range = data.getValidValues();
-            if (range != null) {
-                /*
-                 * If scale_factor and/or add_offset variable attributes are present, then
this is
-                 * a "packed" variable. Otherwise the transfer function is the identity transform.
-                 */
-                final TransferFunction tr = new TransferFunction();
-                final double scale  = data.getAttributeAsNumber(CDM.SCALE_FACTOR);
-                final double offset = data.getAttributeAsNumber(CDM.ADD_OFFSET);
-                if (!Double.isNaN(scale))  tr.setScale (scale);
-                if (!Double.isNaN(offset)) tr.setOffset(offset);
-                final MathTransform1D mt = tr.getTransform();
-                if (!mt.isIdentity()) {
-                    /*
-                     * Heuristic rule defined in UCAR documentation (see EnhanceScaleMissing
interface):
-                     * if the type of the range is equals to the type of the scale, and the
type of the
-                     * data is not wider, then assume that the minimum and maximum are real
values.
-                     */
-                    final int dataType  = data.getDataType().number;
-                    final int rangeType = Numbers.getEnumConstant(range.getElementType());
-                    if (rangeType >= dataType &&
-                        rangeType >= Math.max(Numbers.getEnumConstant(data.getAttributeType(CDM.SCALE_FACTOR)),
-                                              Numbers.getEnumConstant(data.getAttributeType(CDM.ADD_OFFSET))))
-                    {
-                        final boolean isMinIncluded = range.isMinIncluded();
-                        final boolean isMaxIncluded = range.isMaxIncluded();
-                        double minimum = (range.getMinDouble() - offset) / scale;
-                        double maximum = (range.getMaxDouble() - offset) / scale;
-                        if (maximum > minimum) {
-                            final double swap = maximum;
-                            maximum = minimum;
-                            minimum = swap;
-                        }
-                        if (dataType < Numbers.FLOAT && minimum >= Long.MIN_VALUE
&& maximum <= Long.MAX_VALUE) {
-                            range = NumberRange.create(Math.round(minimum), isMinIncluded,
Math.round(maximum), isMaxIncluded);
-                        } else {
-                            range = NumberRange.create(minimum, isMinIncluded, maximum, isMaxIncluded);
-                        }
-                    }
-                }
-                builder.addQuantitative(data.getName(), range, mt, data.getUnit());
+            final SampleDimension[] dimensions = new SampleDimension[data.length];
+            for (int i=0; i<dimensions.length; i++) {
+                dimensions[i] = createSampleDimension(builder, data[i]);
+                builder.clear();
             }
+            definitions = UnmodifiableArrayList.wrap(dimensions);
+        }
+        return definitions;
+    }
+
+    /**
+     * Creates a single sample dimension for the given variable.
+     *
+     * @param  builder  the builder to use for creating the sample dimension.
+     * @param  data     the data for which to create a sample dimension.
+     */
+    private static SampleDimension createSampleDimension(final SampleDimension.Builder builder,
final Variable data) {
+        NumberRange<?> range = data.getValidValues();
+        if (range != null) {
             /*
-             * Adds the "missing value" or "fill value" as qualitative categories.
-             * If a value has both roles, use "missing value" for category name.
+             * If scale_factor and/or add_offset variable attributes are present, then this
is
+             * a "packed" variable. Otherwise the transfer function is the identity transform.
              */
-            boolean setBackground = true;
-            final InternationalString[] names = new InternationalString[2];
-            for (final Map.Entry<Number,Integer> entry : data.getNodataValues().entrySet())
{
-                final Number n = entry.getKey();
-                final double fp = n.doubleValue();
-                if (!builder.rangeCollides(fp, fp)) {
-                    final int role = entry.getValue();          // Bit 0 set (value 1) =
pad value, bit 1 set = missing value.
-                    final int i = (role == 1) ? 1 : 0;          // i=1 if role is only pad
value, i=0 otherwise.
-                    InternationalString name = names[i];
-                    if (name == null) {
-                        name = Vocabulary.formatInternational(i == 0 ? Vocabulary.Keys.MissingValue
: Vocabulary.Keys.FillValue);
-                        names[i] = name;
+            final TransferFunction tr = new TransferFunction();
+            final double scale  = data.getAttributeAsNumber(CDM.SCALE_FACTOR);
+            final double offset = data.getAttributeAsNumber(CDM.ADD_OFFSET);
+            if (!Double.isNaN(scale))  tr.setScale (scale);
+            if (!Double.isNaN(offset)) tr.setOffset(offset);
+            final MathTransform1D mt = tr.getTransform();
+            if (!mt.isIdentity()) {
+                /*
+                 * Heuristic rule defined in UCAR documentation (see EnhanceScaleMissing
interface):
+                 * if the type of the range is equals to the type of the scale, and the type
of the
+                 * data is not wider, then assume that the minimum and maximum are real values.
+                 */
+                final int dataType  = data.getDataType().number;
+                final int rangeType = Numbers.getEnumConstant(range.getElementType());
+                if (rangeType >= dataType &&
+                    rangeType >= Math.max(Numbers.getEnumConstant(data.getAttributeType(CDM.SCALE_FACTOR)),
+                                          Numbers.getEnumConstant(data.getAttributeType(CDM.ADD_OFFSET))))
+                {
+                    final boolean isMinIncluded = range.isMinIncluded();
+                    final boolean isMaxIncluded = range.isMaxIncluded();
+                    double minimum = (range.getMinDouble() - offset) / scale;
+                    double maximum = (range.getMaxDouble() - offset) / scale;
+                    if (maximum > minimum) {
+                        final double swap = maximum;
+                        maximum = minimum;
+                        minimum = swap;
                     }
-                    if (setBackground & (role & 1) != 0) {
-                        setBackground = false;                  // Declare only one fill
value.
-                        builder.setBackground(name, n);
+                    if (dataType < Numbers.FLOAT && minimum >= Long.MIN_VALUE
&& maximum <= Long.MAX_VALUE) {
+                        range = NumberRange.create(Math.round(minimum), isMinIncluded, Math.round(maximum),
isMaxIncluded);
                     } else {
-                        builder.addQualitative(name, n, n);
+                        range = NumberRange.create(minimum, isMinIncluded, maximum, isMaxIncluded);
                     }
                 }
             }
-            definition = builder.build();
+            builder.addQuantitative(data.getName(), range, mt, data.getUnit());
+        }
+        /*
+         * Adds the "missing value" or "fill value" as qualitative categories.
+         * If a value has both roles, use "missing value" for category name.
+         */
+        boolean setBackground = true;
+        final InternationalString[] names = new InternationalString[2];
+        for (final Map.Entry<Number,Integer> entry : data.getNodataValues().entrySet())
{
+            final Number n = entry.getKey();
+            final double fp = n.doubleValue();
+            if (!builder.rangeCollides(fp, fp)) {
+                final int role = entry.getValue();          // Bit 0 set (value 1) = pad
value, bit 1 set = missing value.
+                final int i = (role == 1) ? 1 : 0;          // i=1 if role is only pad value,
i=0 otherwise.
+                InternationalString name = names[i];
+                if (name == null) {
+                    name = Vocabulary.formatInternational(i == 0 ? Vocabulary.Keys.MissingValue
: Vocabulary.Keys.FillValue);
+                    names[i] = name;
+                }
+                if (setBackground & (role & 1) != 0) {
+                    setBackground = false;                  // Declare only one fill value.
+                    builder.setBackground(name, n);
+                } else {
+                    builder.addQualitative(name, n, n);
+                }
+            }
         }
-        return Collections.singletonList(definition);
+        return builder.build();
     }
 
     /**
@@ -211,17 +338,30 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
         if (domain == null) {
             domain = gridGeometry;
         }
-        final Vector samples;
+        final Buffer[] samples = new Buffer[data.length];
         try {
             final GridChange change = new GridChange(domain, gridGeometry);
-            samples = data.read(change.getTargetRange(), change.getTargetStrides());
+            final int[] strides = change.getTargetStrides();
+            for (int i=0; i<samples.length; i++) {
+                // Optional.orElseThrow() below should never fail since Variable.read(…)
wraps primitive array.
+                samples[i] = data[i].read(change.getTargetRange(), strides).buffer().get();
+            }
         } catch (TransformException e) {
             throw new DataStoreReferencingException(e);
         } catch (IOException e) {
             throw new DataStoreException(e);
         }
-        // Optional.orElseThrow() below should never fail since Variable.read(…) wraps
primitive array.
-        return new Image(domain, getSampleDimensions(), data.getDataType().toJava2D(samples.buffer().get()));
+        final DataType type = data[0].getDataType();
+        final DataBuffer buffer;
+        try {
+            buffer = RasterFactory.wrap(type.rasterDataType, samples);
+        } catch (RuntimeException e) {
+            throw new DataStoreContentException(e);
+        }
+        if (buffer == null) {
+            throw new DataStoreContentException(Errors.format(Errors.Keys.UnsupportedType_1,
type.name()));
+        }
+        return new Image(domain, getSampleDimensions(), buffer);
     }
 
     /**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
index 2f18007..a704590 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
@@ -19,7 +19,6 @@ package org.apache.sis.storage.netcdf;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.net.URI;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Collection;
 import org.opengis.util.NameSpace;
@@ -33,8 +32,6 @@ import org.apache.sis.storage.UnsupportedStorageException;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.Aggregate;
 import org.apache.sis.internal.netcdf.Decoder;
-import org.apache.sis.internal.netcdf.Grid;
-import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.storage.URIDataStore;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.setup.OptionKey;
@@ -202,15 +199,7 @@ public class NetcdfStore extends DataStore implements Aggregate {
     public synchronized Collection<Resource> components() throws DataStoreException
{
         if (components == null) try {
             Resource[] resources = decoder.getDiscreteSampling();
-            final List<Resource> grids = new ArrayList<>();
-            for (final Variable variable : decoder.getVariables()) {
-                if (variable.isCoverage()) {
-                    final Grid grid = variable.getGridGeometry(decoder);
-                    if (grid != null) {
-                        grids.add(new GridResource(decoder, grid, variable));
-                    }
-                }
-            }
+            final List<Resource> grids = GridResource.create(decoder);
             if (!grids.isEmpty()) {
                 grids.addAll(UnmodifiableArrayList.wrap(resources));
                 resources = grids.toArray(new Resource[grids.size()]);


Mime
View raw message