sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: First draft of a GridResource.read(…) method returning a GridCoverage.
Date Mon, 10 Dec 2018 01:29:08 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 5a18b91eed194a80ffe3fe7d9d11f774dd7d4ab1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Dec 10 02:28:32 2018 +0100

    First draft of a GridResource.read(…) method returning a GridCoverage.
---
 .../org/apache/sis/util/iso/DefaultTypeName.java   |   4 +-
 .../main/java/org/apache/sis/util/iso/Names.java   |  44 +++++++
 .../org/apache/sis/coverage/SampleDimension.java   |  57 ++++++---
 .../org/apache/sis/coverage/SampleRangeFormat.java |   8 +-
 .../org/apache/sis/coverage/grid/GridCoverage.java | 128 +++++++++++++++++++++
 .../org/apache/sis/coverage/grid/GridGeometry.java |  36 ++++--
 .../sis/internal/raster/ColorModelFactory.java     |  58 ++++++++++
 .../apache/sis/internal/raster/RasterFactory.java  |  83 +++++++++++++
 .../java/org/apache/sis/internal/jdk9/JDK9.java    |  25 +++-
 .../main/java/org/apache/sis/math/ArrayVector.java |  38 ++++++
 .../src/main/java/org/apache/sis/math/Vector.java  |  28 +++++
 .../sis/storage/geotiff/ImageFileDirectory.java    |  14 +++
 .../org/apache/sis/internal/netcdf/DataType.java   |  33 ++++++
 .../apache/sis/storage/netcdf/GridResource.java    |  54 +++++++++
 .../java/org/apache/sis/storage/netcdf/Image.java  |  75 ++++++++++++
 .../sis/internal/storage/AbstractGridResource.java |   6 +-
 .../sis/internal/storage/MetadataBuilder.java      |  70 +++++++----
 .../apache/sis/storage/GridCoverageResource.java   |  18 +++
 18 files changed, 720 insertions(+), 59 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java
index 7a0b96a..e730e38 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java
@@ -144,9 +144,9 @@ public class DefaultTypeName extends DefaultLocalName implements TypeName {
      * The value class to be returned by {@link #toClass()}, or {@code null} if not yet computed.
      * {@link Void#TYPE} is used as a sentinel value meaning explicit {@code null}.
      *
-     * <p>This value is only computed. We do not allow the user to explicitely specify it, because we
+     * <p>This value is only computed. We do not allow the user to explicitly specify it, because we
      * need that {@code DefaultTypeName}s having identical name also have the same {@code valueClass}.
-     * This is necessary {@link DefaultNameFactory#pool} cache integrity. Users who want to explicitely
+     * This is necessary {@link DefaultNameFactory#pool} cache integrity. Users who want to explicitly
      * specify their own value class can override {@link #toClass()} instead.</p>
      *
      * @see #setValueClass(NameSpace, String, Class)
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
index 7d3f083..3d130c9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
@@ -86,6 +86,13 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
  */
 public final class Names extends Static {
     /**
+     * Sequence numbers, created when first needed.
+     *
+     * @see #createMemberName(CharSequence, String, int)
+     */
+    private static final MemberName[] SEQUENCE_NUMBERS = new MemberName[16];
+
+    /**
      * Do not allow instantiation of this class.
      */
     private Names() {
@@ -269,6 +276,43 @@ public final class Names extends Static {
     }
 
     /**
+     * Creates a member name for the given sequence number. The member type will be {@code "OGC:Integer"}.
+     * This method can be used for {@linkplain org.apache.sis.metadata.iso.content.DefaultRangeDimension#setSequenceIdentifier
+     * setting band identifier in metadata} in the common case where band identifier are just numbers.
+     *
+     * @param  namespace  the namespace, or {@code null} for the global namespace.
+     * @param  separator  the separator between the namespace and the local part, or {@code null}
+     *                    for the {@linkplain DefaultNameSpace#DEFAULT_SEPARATOR default separator}.
+     * @param  localPart  the sequence number to use as local part.
+     * @return a member name in the given namespace with the given sequence number.
+     *
+     * @see org.opengis.metadata.content.RangeDimension#getSequenceIdentifier()
+     *
+     * @since 1.0
+     */
+    public static MemberName createMemberName(final CharSequence namespace, String separator, final int localPart) {
+        if (DefaultNameSpace.DEFAULT_SEPARATOR_STRING.equals(separator)) {
+            separator = null;       // For making test for caching easier.
+        }
+        final boolean cached = (namespace == null) && (separator == null) && localPart >= 0 && localPart < SEQUENCE_NUMBERS.length;
+        MemberName name = null;
+        if (cached) synchronized (SEQUENCE_NUMBERS) {
+            name = SEQUENCE_NUMBERS[localPart];
+        }
+        if (name == null) {
+            name = createMemberName(namespace, separator, Integer.toString(localPart), Integer.class);
+            if (cached) synchronized (SEQUENCE_NUMBERS) {
+                /*
+                 * No need to check if a value has been set concurrently because Names.createMemberName(…)
+                 * already checked if an equal instance exists in the current JVM.
+                 */
+                SEQUENCE_NUMBERS[localPart] = name;
+            }
+        }
+        return name;
+    }
+
+    /**
      * Returns the Java class associated to the given type name.
      * The method performs the following choices:
      *
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
index cc8bce8..0e77f72 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -27,6 +27,7 @@ import java.util.Locale;
 import java.util.Objects;
 import java.io.Serializable;
 import javax.measure.Unit;
+import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.apache.sis.referencing.operation.transform.TransferFunction;
@@ -35,7 +36,7 @@ import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.iso.Names;
 import org.apache.sis.util.Numbers;
 
 
@@ -78,13 +79,13 @@ public class SampleDimension implements Serializable {
     private static final long serialVersionUID = 6026936545776852758L;
 
     /**
-     * Description for this sample dimension. Typically used as a way to perform a band select by
+     * Identification for this sample dimension. Typically used as a way to perform a band select by
      * using human comprehensible descriptions instead of just numbers. Web Coverage Service (WCS)
      * can use this name in order to perform band sub-setting as directed from a user request.
      *
      * @see #getName()
      */
-    private final InternationalString name;
+    private final GenericName name;
 
     /**
      * The background value, or {@code null} if unspecified. Should be a sample value of
@@ -160,11 +161,11 @@ public class SampleDimension implements Serializable {
      *
      * <p>Note that {@link Builder} provides a more convenient way to create sample dimensions.</p>
      *
-     * @param name        the sample dimension title or description.
+     * @param name        an identification for the sample dimension.
      * @param background  the background value, or {@code null} if none.
      * @param categories  the list of categories. May be empty if none.
      */
-    public SampleDimension(final InternationalString name, final Number background, final Collection<? extends Category> categories) {
+    public SampleDimension(final GenericName name, final Number background, final Collection<? extends Category> categories) {
         ArgumentChecks.ensureNonNull("name", name);
         ArgumentChecks.ensureNonNull("categories", categories);
         final CategoryList list;
@@ -202,15 +203,15 @@ public class SampleDimension implements Serializable {
     }
 
     /**
-     * Returns a name or description for this sample dimension. This is typically used as a way to perform a band select
+     * Returns an identification for this sample dimension. This is typically used as a way to perform a band select
      * by using human comprehensible descriptions instead of just numbers. Web Coverage Service (WCS) can use this name
      * in order to perform band sub-setting as directed from a user request.
      *
-     * @return the title or description of this sample dimension.
+     * @return an identification of this sample dimension.
      *
      * @see org.opengis.metadata.content.RangeDimension#getSequenceIdentifier()
      */
-    public InternationalString getName() {
+    public GenericName getName() {
         return name;
     }
 
@@ -491,9 +492,9 @@ public class SampleDimension implements Serializable {
      */
     public static class Builder {
         /**
-         * Description for this sample dimension.
+         * Identification for this sample dimension.
          */
-        private CharSequence dimensionName;
+        private GenericName dimensionName;
 
         /**
          * The background value, or {@code null} if unspecified.
@@ -521,32 +522,52 @@ public class SampleDimension implements Serializable {
         }
 
         /**
-         * Sets the name or description of the sample dimension.
+         * Sets an identification of the sample dimension.
          * This is the value to be returned by {@link SampleDimension#getName()}.
          * If this method is invoked more than once, then the last specified name prevails
          * (previous sample dimension names are discarded).
          *
-         * @param  name the name or description of the sample dimension.
+         * @param  name  identification of the sample dimension.
          * @return {@code this}, for method call chaining.
          */
-        public Builder setName(final CharSequence name) {
+        public Builder setName(final GenericName name) {
             dimensionName = name;
             return this;
         }
 
         /**
-         * Sets the name of the sample dimension as a band number.
+         * Sets an identification of the sample dimension as a character sequence.
+         * This is a convenience method for creating a {@link GenericName} from the given characters.
+         *
+         * @param  name  identification of the sample dimension.
+         * @return {@code this}, for method call chaining.
+         */
+        public Builder setName(final CharSequence name) {
+            dimensionName = createLocalName(name);
+            return this;
+        }
+
+        /**
+         * Sets an identification of the sample dimension as a band number.
          * This method should be used only when no more descriptive name is available.
          *
          * @param  band  sequence identifier of the sample dimension to create.
          * @return {@code this}, for method call chaining.
          */
         public Builder setName(final int band) {
-            dimensionName = Vocabulary.formatInternational(Vocabulary.Keys.Band_1, band);
+            dimensionName = Names.createMemberName(null, null, band);
             return this;
         }
 
         /**
+         * A common place where are created local names from character string.
+         * For making easier to revisit if we want to add a namespace.
+         */
+        private static GenericName createLocalName(final CharSequence name) {
+            return Names.createLocalName(null, null, name);
+        }
+
+        /**
          * Creates a range for the given minimum and maximum values. We use the static factory methods instead
          * than the {@link NumberRange} constructor for sharing existing range instances. This is also a way
          * to ensure that the number type is one of the primitive wrappers.
@@ -851,15 +872,15 @@ public class SampleDimension implements Serializable {
          * @return the sample dimension.
          */
         public SampleDimension build() {
-            InternationalString name = Types.toInternationalString(dimensionName);
+            GenericName name = dimensionName;
 defName:    if (name == null) {
                 for (final Category category : categories) {
                     if (category.isQuantitative()) {
-                        name = category.name;
+                        name = createLocalName(category.name);
                         break defName;
                     }
                 }
-                name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
+                name = createLocalName(Vocabulary.formatInternational(Vocabulary.Keys.Untitled));
             }
             return new SampleDimension(name, background, categories);
         }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
index 39d9cb8..8999af1 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
@@ -21,7 +21,7 @@ import java.util.Locale;
 import java.text.NumberFormat;
 import java.io.IOException;
 import java.text.DecimalFormat;
-import org.opengis.util.InternationalString;
+import org.opengis.util.GenericName;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.measure.Range;
 import org.apache.sis.measure.RangeFormat;
@@ -180,7 +180,7 @@ final class SampleRangeFormat extends RangeFormat {
      * @param title       caption for the table.
      * @param categories  the list of categories to format.
      */
-    final String format(final InternationalString title, final CategoryList categories) {
+    final String format(final GenericName title, final CategoryList categories) {
         final StringBuilder buffer = new StringBuilder(800);
         try {
             format(title, categories, buffer);
@@ -209,10 +209,10 @@ final class SampleRangeFormat extends RangeFormat {
      * @param categories  the list of categories to format.
      * @param out         where to write the category table.
      */
-    void format(final InternationalString title, final CategoryList categories, final Appendable out) throws IOException {
+    void format(final GenericName title, final CategoryList categories, final Appendable out) throws IOException {
         prepare(categories);
         final String lineSeparator = System.lineSeparator();
-        out.append(title.toString(getLocale())).append(lineSeparator);
+        out.append(title.toInternationalString().toString(getLocale())).append(lineSeparator);
         /*
          * Write table header: │ Values │ Measures │ name │
          */
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
new file mode 100644
index 0000000..300de53
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage.grid;
+
+import java.util.List;
+import java.util.Collection;
+import java.awt.image.RenderedImage;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * Base class of coverages with domains defined as a set of grid points.
+ * The essential property of coverage is to be able to generate a value for any point within its domain.
+ * Since a grid coverage is represented by a grid of values, the value returned by the coverage for a point
+ * is that of the grid value whose location is nearest the point.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public abstract class GridCoverage {
+    /**
+     * The grid extent, coordinate reference system (CRS) and conversion from cell indices to CRS.
+     */
+    private final GridGeometry gridGeometry;
+
+    /**
+     * List of sample dimension (band) information for the grid coverage. Information include such things
+     * as description, the no data values, minimum and maximum values, <i>etc</i>. A coverage must have
+     * at least one sample dimension. The content of this array shall never be modified.
+     */
+    private final SampleDimension[] sampleDimensions;
+
+    /**
+     * Constructs a grid coverage using the specified grid geometry and sample dimensions.
+     *
+     * @param grid   the grid extent, CRS and conversion from cell indices to CRS.
+     * @param bands  sample dimensions for each image band.
+     */
+    protected GridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands) {
+        ArgumentChecks.ensureNonNull("grid",  grid);
+        ArgumentChecks.ensureNonNull("bands", bands);
+        gridGeometry = grid;
+        sampleDimensions = bands.toArray(new SampleDimension[bands.size()]);
+        for (int i=0; i<sampleDimensions.length; i++) {
+            ArgumentChecks.ensureNonNullElement("bands", i, sampleDimensions[i]);
+        }
+    }
+
+    /**
+     * Returns the coordinate reference system to which the values in grid domain are referenced.
+     * This is the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
+     * This coordinate reference system is usually different than the coordinate system of the grid.
+     * It is the target coordinate reference system of the {@link GridGeometry#getGridToCRS gridToCRS}
+     * math transform.
+     *
+     * <p>The default implementation delegates to {@link GridGeometry#getCoordinateReferenceSystem()}.</p>
+     *
+     * @return the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
+     * @throws IncompleteGridGeometryException if the grid geometry has no CRS.
+     */
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return gridGeometry.getCoordinateReferenceSystem();
+    }
+
+    /**
+     * Returns information about the <cite>domain</cite> of this grid coverage.
+     * Information includes the grid extent, CRS and conversion from cell indices to CRS.
+     * {@code GridGeometry} can also provide derived information like bounding box and resolution.
+     *
+     * @return grid extent, CRS and conversion from cell indices to CRS.
+     */
+    public GridGeometry getGridGeometry() {
+        return gridGeometry;
+    }
+
+    /**
+     * Returns information about the <cite>range</cite> of this grid coverage.
+     * Information include names, sample value ranges, fill values and transfer functions for all bands in this grid coverage.
+     *
+     * @return names, value ranges, fill values and transfer functions for all bands in this grid coverage.
+     */
+    public List<SampleDimension> getSampleDimensions() {
+        return UnmodifiableArrayList.wrap(sampleDimensions);
+    }
+
+    /**
+     * Returns a two-dimensional slice of grid data as a rendered image.
+     * This method tries to return a view as much as possible (i.e. sample values are not copied).
+     *
+     * @param  xAxis  dimension to use for <var>x</var> axis.
+     * @param  yAxis  dimension to use for <var>y</var> axis.
+     * @return the grid data as a rendered image in the given CRS dimensions.
+     */
+    public abstract RenderedImage asRenderedImage(int xAxis, int yAxis);
+
+    /**
+     * Returns a string representation of this grid coverage for debugging purpose.
+     *
+     * @return a string representation of this grid coverage for debugging purpose.
+     */
+    @Override
+    public String toString() {
+        final String lineSeparator = System.lineSeparator();
+        final StringBuilder buffer = new StringBuilder(1000);
+        buffer.append("Grid coverage domain:").append(lineSeparator);
+        gridGeometry.formatTo(buffer, lineSeparator + "  ");
+        return buffer.toString();
+    }
+}
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 9f10131..aa8a5ca 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
@@ -903,7 +903,15 @@ public class GridGeometry implements Serializable {
             throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentValue_2, "bitmask", bitmask));
         }
-        return new Formatter(bitmask).toString();
+        return new Formatter(new StringBuilder(512), bitmask, System.lineSeparator()).toString();
+    }
+
+    /**
+     * Formats a string representation of this grid geometry in the specified buffer.
+     * This is used for reducing the amount of copies in {@link GridCoverage#toString()}.
+     */
+    final void formatTo(final StringBuilder out, final String lineSeparator) {
+        new Formatter(out, EXTENT | ENVELOPE | CRS | GRID_TO_CRS | RESOLUTION, lineSeparator).format();
     }
 
     /**
@@ -950,14 +958,14 @@ public class GridGeometry implements Serializable {
          * Creates a new formatter for the given combination of {@link #EXTENT}, {@link #ENVELOPE},
          * {@link #CRS}, {@link #GRID_TO_CRS} and {@link #RESOLUTION}.
          */
-        Formatter(final int bitmask) {
-            this.bitmask  = bitmask;
-            lineSeparator = System.lineSeparator();
-            locale        = Locale.getDefault(Locale.Category.DISPLAY);
-            vocabulary    = Vocabulary.getResources(locale);
-            buffer        = new StringBuilder(512);
-            crs           = (envelope != null) ? envelope.getCoordinateReferenceSystem() : null;
-            cs            = (crs != null) ? crs.getCoordinateSystem() : null;
+        Formatter(final StringBuilder out, final int bitmask, final String lineSeparator) {
+            this.buffer        = out;
+            this.bitmask       = bitmask;
+            this.lineSeparator = lineSeparator;
+            this.locale        = Locale.getDefault(Locale.Category.DISPLAY);
+            this.vocabulary    = Vocabulary.getResources(locale);
+            this.crs           = (envelope != null) ? envelope.getCoordinateReferenceSystem() : null;
+            this.cs            = (crs != null) ? crs.getCoordinateSystem() : null;
         }
 
         /**
@@ -965,6 +973,15 @@ public class GridGeometry implements Serializable {
          */
         @Override
         public final String toString() {
+            format();
+            return buffer.toString();
+        }
+
+        /**
+         * Formats a string representation of the enclosing {@link GridGeometry} instance
+         * in the buffer specified at construction time.
+         */
+        final void format() {
             /*
              * Example: Grid extent
              * ├─ Dimension 0: [370 … 389]  (20 cells)
@@ -1056,7 +1073,6 @@ public class GridGeometry implements Serializable {
                     buffer.append(lineSeparator);
                 }
             }
-            return buffer.toString();
         }
 
         /**
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
new file mode 100644
index 0000000..f03de49
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.raster;
+
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+
+/**
+ * Creates color models from given properties.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final class ColorModelFactory {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private ColorModelFactory() {
+    }
+
+    /**
+     * Creates for image of the given type.
+     *
+     * @param  type  one of {@link DataBuffer} constant.
+     * @return color model.
+     *
+     * @todo need much improvement.
+     */
+    public static ColorModel create(final int type) {
+        final int bits = DataBuffer.getDataTypeSize(type);
+        final int[] ARGB = new int[1 << bits];
+        final float scale = 255f / ARGB.length;
+        for (int i=0; i<ARGB.length; i++) {
+            int c = Math.round(scale * i);
+            c |= (c << 8) | (c << 16);
+            ARGB[i] = c;
+        }
+        return new IndexColorModel(bits, ARGB.length, ARGB, 0, false, -1, DataBuffer.TYPE_USHORT);
+    }
+}
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
new file mode 100644
index 0000000..009c1a5
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.raster;
+
+import java.awt.Point;
+import java.awt.image.DataBuffer;
+import java.awt.image.SampleModel;
+import java.awt.image.BandedSampleModel;
+import java.awt.image.WritableRaster;
+import org.apache.sis.util.Static;
+
+
+/**
+ * Creates rasters from given properties.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final class RasterFactory extends Static {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private RasterFactory() {
+    }
+
+    /**
+     * Wraps the given data buffer in a raster.
+     *
+     * @param  buffer          buffer that contains the sample values.
+     * @param  width           raster width in pixels.
+     * @param  height          raster height in pixels.
+     * @param  scanlineStride  line stride of raster data.
+     * @param  bankIndices     bank indices for each band, or {@code null} for 0, 1, 2, 3….
+     * @param  bandOffsets     offsets of all bands, or {@code null} for all 0.
+     * @param  location        the upper-left corner of the raster, or {@code null} for (0,0).
+     * @return a raster built from given properties.
+     *
+     * @see WritableRaster#createBandedRaster(DataBuffer, int, int, int, int[], int[], Point)
+     */
+    public static WritableRaster createBandedRaster​(final DataBuffer buffer,
+            final int width, final int height, final int scanlineStride,
+            int[] bankIndices, int[] bandOffsets, final Point location)
+    {
+        if (bankIndices == null) {
+            bankIndices = new int[buffer.getNumBanks()];
+            for (int i=1; i<bankIndices.length; i++) {
+                bankIndices[i] = i;
+            }
+        }
+        if (bandOffsets == null) {
+            bandOffsets = new int[bankIndices.length];
+        }
+        final int dataType = buffer.getDataType();
+        switch (dataType) {
+            case DataBuffer.TYPE_BYTE:
+            case DataBuffer.TYPE_USHORT:
+            case DataBuffer.TYPE_INT: {
+                // This constructor supports only above-cited types.
+                return WritableRaster.createBandedRaster(buffer, width, width, scanlineStride, bankIndices, bandOffsets, location);
+            }
+            default: {
+                SampleModel model = new BandedSampleModel(dataType, width, height, scanlineStride, bankIndices, bandOffsets);
+                return WritableRaster.createWritableRaster(model, buffer, location);
+            }
+        }
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
index 8f20a5d..d05a107 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
@@ -16,6 +16,13 @@
  */
 package org.apache.sis.internal.jdk9;
 
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
 import java.util.Arrays;
 import java.util.Set;
 import java.util.List;
@@ -29,7 +36,7 @@ import org.apache.sis.internal.util.UnmodifiableArrayList;
  * This file will be deleted on the SIS JDK9 branch.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @since   0.8
+ * @since   1.0
  * @version 0.8
  * @module
  */
@@ -71,4 +78,20 @@ public final class JDK9 {
             default: return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(elements)));
         }
     }
+
+    /**
+     * Place holder for {@code Buffer.slice()}.
+     *
+     * @param  b the buffer to slice.
+     * @return the sliced buffer.
+     */
+    public static Buffer slice(Buffer b) {
+        if (b instanceof ByteBuffer)   return ((ByteBuffer) b).slice();
+        if (b instanceof ShortBuffer)  return ((ShortBuffer) b).slice();
+        if (b instanceof IntBuffer)    return ((IntBuffer) b).slice();
+        if (b instanceof LongBuffer)   return ((LongBuffer) b).slice();
+        if (b instanceof FloatBuffer)  return ((FloatBuffer) b).slice();
+        if (b instanceof DoubleBuffer) return ((DoubleBuffer) b).slice();
+        throw new IllegalArgumentException();
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java b/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java
index 8936dc9..f6a4a70 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java
@@ -17,6 +17,13 @@
 package org.apache.sis.math;
 
 import java.io.Serializable;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.FloatBuffer;
+import java.nio.DoubleBuffer;
 import java.util.Arrays;
 import java.util.function.IntSupplier;
 import org.apache.sis.util.Numbers;
@@ -85,6 +92,7 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
      * or {@code null} if this method can not do better than the given {@code Vector} instance.
      * This method shall be invoked only for vector of integer values (this is not verified).
      */
+    @SuppressWarnings("null")
     static Vector compress(final Vector source, final long min, final long max) {
         boolean isSigned = (min >= Byte.MIN_VALUE && max <= Byte.MAX_VALUE);
         if (isSigned || (min >= 0 && max <= 0xFF)) {
@@ -279,6 +287,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
             return NumberRange.create(min, true, max, true);
         }
 
+        /** Wraps this vector in a buffer. */
+        @Override public Buffer buffer() {
+            return DoubleBuffer.wrap(array);
+        }
+
         /** Returns a copy of current data as a floating point array. */
         @Override public double[] doubleValues() {
             return array.clone();
@@ -389,6 +402,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
             return NumberRange.create(min, true, max, true);
         }
 
+        /** Wraps this vector in a buffer. */
+        @Override public final Buffer buffer() {
+            return FloatBuffer.wrap(array);
+        }
+
         /** Returns a copy of current data as a floating point array. */
         @Override public final float[] floatValues() {
             return array.clone();
@@ -548,6 +566,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
             return null;
         }
 
+        /** Wraps this vector in a buffer. */
+        @Override public final Buffer buffer() {
+            return LongBuffer.wrap(array);
+        }
+
         /** Applies hash code contract specified {@link Vector#hashCode()}. */
         @Override public final int hashCode() {
             return Arrays.hashCode(array);
@@ -665,6 +688,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
             return null;
         }
 
+        /** Wraps this vector in a buffer. */
+        @Override public final Buffer buffer() {
+            return IntBuffer.wrap(array);
+        }
+
         /** Applies hash code contract specified {@link Vector#hashCode()}. */
         @Override public final int hashCode() {
             return Arrays.hashCode(array);
@@ -757,6 +785,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
          * performed by Java would make the implementation a little bit more tricky.
          */
 
+        /** Wraps this vector in a buffer. */
+        @Override public final Buffer buffer() {
+            return ShortBuffer.wrap(array);
+        }
+
         /** Applies hash code contract specified {@link Vector#hashCode()}. */
         @Override public final int hashCode() {
             return Arrays.hashCode(array);
@@ -850,6 +883,11 @@ abstract class ArrayVector<E extends Number> extends Vector implements CheckedCo
          * performed by Java would make the implementation a little bit more tricky.
          */
 
+        /** Wraps this vector in a buffer. */
+        @Override public final Buffer buffer() {
+            return ByteBuffer.wrap(array);
+        }
+
         /** Applies hash code contract specified {@link Vector#hashCode()}. */
         @Override public final int hashCode() {
             return Arrays.hashCode(array);
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 8a4af37..fc384a6 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
@@ -17,11 +17,13 @@
 package org.apache.sis.math;
 
 import java.io.Serializable;
+import java.nio.Buffer;
 import java.util.Arrays;
 import java.util.AbstractList;
 import java.util.RandomAccess;
 import java.util.StringJoiner;
 import java.util.function.IntSupplier;
+import org.apache.sis.internal.jdk9.JDK9;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArraysExt;
@@ -941,6 +943,17 @@ search:     for (;;) {
             final Vector c = super.compress(tolerance);
             return (c != this) ? c : copy();
         }
+
+        /**
+         * Returns a buffer over the sub-section represented by this {@code SubSampling} instance.
+         */
+        @Override
+        public Buffer buffer() {
+            if (step == 1) {
+                return JDK9.slice(Vector.this.buffer().position(first).limit(first + length));
+            }
+            return super.buffer();
+        }
     }
 
     /**
@@ -1257,6 +1270,21 @@ search:     for (;;) {
     }
 
     /**
+     * Returns the vector data as a {@code java.nio} buffer.
+     * Data are not copied: changes in the buffer are reflected on this vector and vice-versa.
+     * Date are provided in their "raw" form. For example unsigned integers are given as plain {@code int} elements
+     * and it is caller responsibility to use {@link Integer#toUnsignedLong(int)} if needed.
+     *
+     * @return the vector data as a buffer.
+     * @throws UnsupportedOperationException if this vector can not be represented by a buffer.
+     *
+     * @since 1.0
+     */
+    public Buffer buffer() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Copies all values in an array of double precision floating point numbers.
      * This method is for inter-operability with APIs requiring an array of primitive type.
      *
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 022d5c2..5beda8d 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -37,6 +37,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.SampleDimension;
@@ -1278,6 +1279,19 @@ final class ImageFileDirectory extends AbstractGridResource {
     }
 
     /**
+     * Loads a subset of the grid coverage represented by this resource.
+     *
+     * @param  domain  desired grid extent and resolution, or {@code null} for reading the whole domain.
+     * @param  range   0-based index of sample dimensions to read, or an empty sequence for reading all ranges.
+     * @return the grid coverage for the specified domain and range.
+     * @throws DataStoreException if an error occurred while reading the grid coverage data.
+     */
+    @Override
+    public GridCoverage read(final GridGeometry domain, final int... range) throws DataStoreException {
+        throw new DataStoreException("Not yet implemented.");   // TODO
+    }
+
+    /**
      * Reports a warning with a message created from the given resource keys and parameters.
      *
      * @param  level       the logging level for the message to log.
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 b86a8cc..8b6a45b 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,8 +16,17 @@
  */
 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;
 
 
 /**
@@ -190,6 +199,30 @@ 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/storage/netcdf/GridResource.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
index 6b1ba58..4c69e52 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
@@ -32,11 +32,15 @@ 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.GridExtent;
+import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.math.Vector;
 import ucar.nc2.constants.CDM;                      // We use only String constants.
 
 
@@ -222,6 +226,56 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
     }
 
     /**
+     * Loads a subset of the grid coverage represented by this resource.
+     *
+     * @param  domain  desired grid extent and resolution, or {@code null} for reading the whole domain.
+     * @param  range   0-based index of sample dimensions to read, or an empty sequence for reading all ranges.
+     * @return the grid coverage for the specified domain and range.
+     * @throws DataStoreException if an error occurred while reading the grid coverage data.
+     */
+    @Override
+    public GridCoverage read(GridGeometry domain, final int... range) throws DataStoreException {
+        if (range != null && range.length != 0) {
+            throw new DataStoreException("Unsupported range subsetting.");      // TODO
+        }
+        if (domain != null) {
+            throw new DataStoreException("Unsupported domain subsetting.");      // TODO
+        }
+        domain = getGridGeometry();
+        final GridExtent extent = domain.getExtent();
+        final int   dimension   = domain.getDimension();
+        final int[] areaLower   = new int[dimension];
+        final int[] areaUpper   = new int[dimension];
+        final int[] subsampling = new int[dimension];
+        for (int i=0; i<dimension; i++) {
+            final int j = (dimension - 1) - i;
+            areaLower[j] = unsigned(extent.getLow (i));             // Inclusive.
+            areaUpper[j] = unsigned(extent.getHigh(i) + 1);         // Exclusive.
+            if (i >= 2) {
+                areaUpper[j] = areaLower[j] + 1;                    // TODO
+            }
+            subsampling[j] = 1;
+        }
+        final Vector samples;
+        try {
+            samples = data.read(areaLower, areaUpper, subsampling);
+        } catch (IOException e) {
+            throw new DataStoreException(e);
+        }
+        return new Image(domain, getSampleDimensions(), data.getDataType().toJava2D(samples.buffer()));
+    }
+
+    /**
+     * Returns the given value as an unsigned integer.
+     */
+    private static int unsigned(final long value) throws DataStoreException {
+        if (value < 0L || value > 0xFFFFFFFFL) {
+            throw new DataStoreException(Errors.format(Errors.Keys.IndexOutOfBounds_1, value));
+        }
+        return (int) value;
+    }
+
+    /**
      * Gets the paths to files used by this resource, or an empty array if unknown.
      */
     @Override
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
new file mode 100644
index 0000000..027734d
--- /dev/null
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.netcdf;
+
+import java.util.List;
+import java.awt.image.DataBuffer;
+import java.awt.image.ColorModel;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.internal.raster.RasterFactory;
+import org.apache.sis.internal.raster.ColorModelFactory;
+
+
+/**
+ * Data loaded from a {@link GridResource}.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class Image extends GridCoverage {
+    /**
+     * The sample values.
+     */
+    private final DataBuffer data;
+
+    /**
+     * Creates a new raster from the given resource.
+     */
+    Image(final GridGeometry domain, final List<SampleDimension> range, final DataBuffer data) {
+        super(domain, range);
+        this.data = data;
+    }
+
+    /**
+     * Returns a two-dimensional slice of grid data as a rendered image.
+     * This method tries to return a view as much as possible (i.e. sample values are not copied).
+     *
+     * @param  xAxis  dimension to use for <var>x</var> axis.
+     * @param  yAxis  dimension to use for <var>y</var> axis.
+     * @return the grid data as a rendered image in the given CRS dimensions.
+     */
+    @Override
+    public RenderedImage asRenderedImage(final int xAxis, final int yAxis) {
+        if (xAxis != 0 || yAxis != 1) {
+            throw new IllegalArgumentException();       // TODO
+        }
+        final GridExtent extent = getGridGeometry().getExtent();
+        final int width  = Math.toIntExact(extent.getSize(xAxis));
+        final int height = Math.toIntExact(extent.getSize(yAxis));
+        final WritableRaster raster = RasterFactory.createBandedRaster(data, width, height, width, null, null, null);
+        final ColorModel colors = ColorModelFactory.create(data.getDataType());
+        return new BufferedImage(colors, raster, false, null);
+    }
+}
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 5590872..bd06abb 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,13 +16,14 @@
  */
 package org.apache.sis.internal.storage;
 
+import org.opengis.geometry.Envelope;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.util.logging.WarningListeners;
-import org.opengis.geometry.Envelope;
 
 
 /**
@@ -81,5 +82,8 @@ public abstract class AbstractGridResource extends AbstractResource implements G
     protected void createMetadata(final MetadataBuilder metadata) throws DataStoreException {
         super.createMetadata(metadata);
         metadata.addSpatialRepresentation(null, getGridGeometry(), false);
+        for (final SampleDimension band : getSampleDimensions()) {
+            metadata.addNewBand(band);
+        }
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
index 2ff8150..26b8e8c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
@@ -119,6 +119,7 @@ import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.metadata.sql.MetadataSource;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.iso.Names;
@@ -148,11 +149,6 @@ import org.opengis.metadata.citation.Responsibility;
  */
 public class MetadataBuilder {
     /**
-     * Band numbers, created when first needed.
-     */
-    private static final MemberName[] BAND_NUMBERS = new MemberName[16];
-
-    /**
      * Whether the next party to create should be an instance of {@link DefaultIndividual} or {@link DefaultOrganisation}.
      *
      * @see #party()
@@ -1896,7 +1892,7 @@ parse:      for (int i = 0; i < length;) {
 
     /**
      * Adds and populates a "spatial representation info" node using the given grid geometry.
-     * If this method invokes implicitly {@link #newGridRepresentation(GridType)}, unless this
+     * This method invokes implicitly {@link #newGridRepresentation(GridType)}, unless this
      * method returns {@code false} in which case nothing has been done.
      * Storage location is:
      *
@@ -2231,6 +2227,49 @@ parse:      for (int i = 0; i < length;) {
     }
 
     /**
+     * Sets the sequence identifier, sample value ranges, transfer function and units of measurement
+     * from the given sample dimension. This method dispatch its work to other methods in this class.
+     * Before to set any value, this method starts a new band by calling {@link #newSampleDimension()}.
+     * Storage locations are:
+     *
+     * <ul>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/sequenceIdentifier}</li>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/minValue}</li>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/maxValue}</li>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/scale}</li>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/offset}</li>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/transferFunctionType}</li>
+     *   <li>{@code metadata/contentInfo/attributeGroup/attribute/unit}</li>
+     * </ul>
+     *
+     * @param  band  the sample dimension to describe in metadata, or {@code null} if none.
+     */
+    public final void addNewBand(final SampleDimension band) {
+        if (band != null) {
+            newSampleDimension();
+            final GenericName g = band.getName();
+            if (g != null) {
+                final MemberName name;
+                if (g instanceof MemberName) {
+                    name = (MemberName) g;
+                } else {
+                    name = Names.createMemberName(null, null, g.tip().toString(), String.class);
+                }
+                setBandIdentifier(name);
+            }
+            band.getSampleRange().ifPresent((range) -> {
+                addMinimumSampleValue(range.getMinDouble(true));
+                addMaximumSampleValue(range.getMaxDouble(true));
+            });
+            band.getTransferFunctionFormula().ifPresent((tr) -> {
+                setTransferFunction(tr.getScale(), tr.getOffset());
+                sampleDimension().setTransferFunctionType(tr.getType());
+            });
+            band.getUnits().ifPresent((unit) -> setSampleUnits(unit));
+        }
+    }
+
+    /**
      * Sets the name or number that uniquely identifies instances of bands of wavelengths on which a sensor operates.
      * If a coverage contains more than one band, additional bands can be created by calling
      * {@link #newSampleDimension()} before to call this method.
@@ -2257,28 +2296,13 @@ parse:      for (int i = 0; i < length;) {
      */
     public final void setBandIdentifier(final int sequenceIdentifier) {
         if (sequenceIdentifier > 0) {
-            final boolean cached = (sequenceIdentifier <= BAND_NUMBERS.length);
-            MemberName name = null;
-            if (cached) synchronized (BAND_NUMBERS) {
-                name = BAND_NUMBERS[sequenceIdentifier - 1];
-            }
-            if (name == null) {
-                name = Names.createMemberName(null, null, String.valueOf(sequenceIdentifier), Integer.class);
-                if (cached) synchronized (BAND_NUMBERS) {
-                    /*
-                     * No need to check if a value has been set concurrently because Names.createMemberName(…)
-                     * already checked if an equal instance exists in the current JVM.
-                     */
-                    BAND_NUMBERS[sequenceIdentifier - 1] = name;
-                }
-            }
-            setBandIdentifier(name);
+            setBandIdentifier(Names.createMemberName(null, null, sequenceIdentifier));
         }
     }
 
     /**
      * Adds an identifier for the current band.
-     * These identifiers can be use to provide names for the attribute from a standard set of names.
+     * These identifiers can be used to provide names for the attribute from a standard set of names.
      * If a coverage contains more than one band, additional bands can be created by calling
      * {@link #newSampleDimension()} before to call this method.
      * Storage location is:
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 a6e10ec..6e9bd6e 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
@@ -19,6 +19,7 @@ package org.apache.sis.storage;
 import java.util.List;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridCoverage;
 
 
 /**
@@ -71,4 +72,21 @@ public interface GridCoverageResource extends DataSet {
      * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
      */
     List<SampleDimension> getSampleDimensions() throws DataStoreException;
+
+    /**
+     * Loads a subset of the grid coverage represented by this resource. If a non-null grid geometry is specified,
+     * then this method will try to return a grid coverage matching the given grid geometry on a best-effort basis;
+     * the coverage actually returned may have a different resolution, cover a different area in a different CRS,
+     * <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>While this method name suggests an immediate reading, some implementations may defer the actual reading
+     * at a later stage.</p>
+     *
+     * @param  domain  desired grid extent and resolution, or {@code null} for reading the whole domain.
+     * @param  range   0-based index of sample dimensions to read, or an empty sequence for reading all ranges.
+     * @return the grid coverage for the specified domain and range.
+     * @throws DataStoreException if an error occurred while reading the grid coverage data.
+     */
+    GridCoverage read(GridGeometry domain, int... range) throws DataStoreException;
 }


Mime
View raw message