You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/09/03 16:52:30 UTC

[sis] 03/05: Landsat data store use GeoTIFF data store for providing components.

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 d585a5b3fe11c09999c40926a447001687965a9c
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Sep 3 18:37:41 2021 +0200

    Landsat data store use GeoTIFF data store for providing components.
---
 storage/sis-earth-observation/pom.xml              |   5 +
 .../apache/sis/storage/earthobservation/Band.java  |   7 +-
 .../sis/storage/earthobservation/BandData.java     | 131 ++++++++++++
 .../storage/earthobservation/LandsatReader.java    |   3 +-
 .../sis/storage/earthobservation/LandsatStore.java |  82 +++++++-
 .../org/apache/sis/storage/geotiff/DataCube.java   |  11 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |   9 +-
 .../sis/internal/storage/GridResourceWrapper.java  | 220 +++++++++++++++++++++
 8 files changed, 460 insertions(+), 8 deletions(-)

diff --git a/storage/sis-earth-observation/pom.xml b/storage/sis-earth-observation/pom.xml
index 0b8a1cf..f224bf9 100644
--- a/storage/sis-earth-observation/pom.xml
+++ b/storage/sis-earth-observation/pom.xml
@@ -125,6 +125,11 @@
       <artifactId>sis-storage</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.sis.storage</groupId>
+      <artifactId>sis-geotiff</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 
 </project>
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java
index 44e65a7..4210320 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.storage.earthobservation;
 
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.iso.SimpleInternationalString;
+
 
 /**
  * Names of Landsat bands.
@@ -50,7 +53,7 @@ enum Band {
     /**
      * Name of Landsat band.
      */
-    final String name;
+    final InternationalString name;
 
     /**
      * Peak response wavelength for the Landsat band, in nanometres.
@@ -62,7 +65,7 @@ enum Band {
      */
     private Band(final BandGroup group, final String name, final short wavelength) {
         this.group      = group;
-        this.name       = name;
+        this.name       = new SimpleInternationalString(name);
         this.wavelength = wavelength;
     }
 }
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandData.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandData.java
new file mode 100644
index 0000000..804f1ab
--- /dev/null
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandData.java
@@ -0,0 +1,131 @@
+/*
+ * 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.earthobservation;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import org.opengis.util.LocalName;
+import org.opengis.util.GenericName;
+import org.opengis.metadata.Metadata;
+import org.opengis.metadata.identification.Identification;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.geotiff.GeoTiffStore;
+import org.apache.sis.internal.storage.GridResourceWrapper;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.DefaultMetadata;
+
+
+/**
+ * A band in a Landsat data set. Each band is represented by a separated GeoTIFF file.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class BandData extends GridResourceWrapper {
+    /**
+     * The data store that contains this band.
+     * Also the object on which to perform synchronization locks.
+     */
+    private final LandsatStore parent;
+
+    /**
+     * The band for which this instance provides data.
+     */
+    private final Band band;
+
+    /**
+     * Identifier of the band for which this instance provides data.
+     *
+     * @see #getIdentifier()
+     */
+    private final LocalName identifier;
+
+    /**
+     * Filename of the file to read for band data.
+     * This is relative to {@link LandsatStore#directory}.
+     */
+    private final String filename;
+
+    /**
+     * The metadata, created when first needed.
+     */
+    private Metadata metadata;
+
+    /**
+     * Creates a new resource for the band identified by the given identifier.
+     */
+    BandData(final LandsatStore parent, final Band band, final LocalName identifier, final String filename) {
+        this.parent     = parent;
+        this.band       = band;
+        this.identifier = identifier;
+        this.filename   = filename;
+    }
+
+    /**
+     * Creates the GeoTIFF reader and get the first image from it.
+     */
+    @Override
+    protected GridCoverageResource createSource() throws DataStoreException {
+        final Path file;
+        if (parent.directory != null) {
+            file = parent.directory.resolve(filename);
+        } else {
+            file = Paths.get(filename);
+        }
+        return new GeoTiffStore(parent, parent.getProvider(), new StorageConnector(file), true).components().get(0);
+    }
+
+    /**
+     * Returns the resource persistent identifier. The name is the {@link Band#name()}
+     * and the scope (namespace) is the name of the directory that contains this band.
+     */
+    @Override
+    public Optional<GenericName> getIdentifier() throws DataStoreException {
+        return Optional.of(identifier);
+    }
+
+    /**
+     * Returns information about this resource.
+     */
+    @Override
+    public synchronized Metadata getMetadata() throws DataStoreException {
+        if (metadata == null) {
+            /*
+             * The GeoTIFF store implementation is known to create instances of `DefaultMetadata`,
+             * and this instance is kept modifiable because the `hidden` argument specified to the
+             * `GeoTiffStore` constructor was `true`. This is an undocumented feature that may be
+             * modified in future SIS versions.
+             */
+            final DefaultMetadata md = (DefaultMetadata) source().getMetadata();
+            for (final Identification id : md.getIdentificationInfo()) {
+                final DefaultCitation c = (DefaultCitation) id.getCitation();
+                if (c != null) {
+                    c.setTitle(band.name);
+                    break;
+                }
+            }
+            md.transitionTo(DefaultMetadata.State.FINAL);
+            metadata = md;
+        }
+        return metadata;
+    }
+}
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index d914cb0..7691f70 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -60,7 +60,6 @@ import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 import org.apache.sis.internal.referencing.provider.PolarStereographicB;
@@ -804,7 +803,7 @@ final class LandsatReader extends MetadataBuilder {
         DefaultBand band = bands.get(bk);
         if (band == null) {
             band = new DefaultBand();
-            band.setDescription(new SimpleInternationalString(bk.name));
+            band.setDescription(bk.name);
             band.setPeakResponse((double) bk.wavelength);
             band.setBoundUnits(Units.NANOMETRE);
             bands.put(bk, band);
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
index 992afed..f550865 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.storage.earthobservation;
 
+import java.util.Map;
+import java.util.List;
 import java.util.Optional;
 import java.io.Reader;
 import java.io.BufferedReader;
@@ -26,10 +28,15 @@ import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.nio.file.Files;
 import java.nio.file.StandardOpenOption;
-import org.opengis.metadata.Metadata;
+import org.opengis.util.NameSpace;
+import org.opengis.util.LocalName;
 import org.opengis.util.GenericName;
+import org.opengis.util.NameFactory;
 import org.opengis.util.FactoryException;
+import org.opengis.metadata.Metadata;
 import org.opengis.parameter.ParameterValueGroup;
+import org.apache.sis.storage.Aggregate;
+import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreClosedException;
@@ -40,6 +47,8 @@ import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.storage.event.WarningEvent;
 import org.apache.sis.internal.storage.URIDataStore;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.setup.OptionKey;
 
 
@@ -76,7 +85,7 @@ import org.apache.sis.setup.OptionKey;
  * @since   0.8
  * @module
  */
-public class LandsatStore extends DataStore {
+public class LandsatStore extends DataStore implements Aggregate {
     /**
      * The reader, or {@code null} if closed.
      */
@@ -103,6 +112,12 @@ public class LandsatStore extends DataStore {
     private GenericName identifier;
 
     /**
+     * The array of images for each Landsat band, or {@code null} if not yet created.
+     * This array is created together with {@linkplain #metadata} and is unmodifiable.
+     */
+    private BandData[] components;
+
+    /**
      * Creates a new Landsat store from the given file, URL, stream or character reader.
      * This constructor invokes {@link StorageConnector#closeAllExcept(Object)},
      * keeping open only the needed resource.
@@ -142,6 +157,16 @@ public class LandsatStore extends DataStore {
     }
 
     /**
+     * Returns the name of the directory that contains this data set.
+     * The directory may not exist, for example if the data are read from a ZIP file.
+     * The returned name can be used in user interfaces or in error messages.
+     */
+    @Override
+    public final String getDisplayName() {
+        return (directory != null) ? directory.getFileName().toString() : super.getDisplayName();
+    }
+
+    /**
      * Returns the parameters used to open this Landsat data store.
      * The parameters are described by {@link LandsatStoreProvider#getOpenParameters()} and contains at least
      * a parameter named {@value org.apache.sis.storage.DataStoreProvider#LOCATION} with a {@link URI} value.
@@ -177,6 +202,7 @@ public class LandsatStore extends DataStore {
 
     /**
      * Parses the main Landsat text file.
+     * Also creates the array of components, but without loading GeoTIFF data yet.
      */
     private void loadMetadata() throws DataStoreException {
         if (source == null) {
@@ -187,6 +213,20 @@ public class LandsatStore extends DataStore {
             final LandsatReader parser = new LandsatReader(getDisplayName(), listeners);
             parser.read(reader);
             metadata = parser.getMetadata();
+            /*
+             * Create the array of components. The resource identifier is the band name.
+             * The namespace of each identifier is the name of the data set directory.
+             */
+            final String      name    = getDisplayName();
+            final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
+            final NameSpace   scope   = (name != null) ? factory.createNameSpace(factory.createLocalName(null, name), null) : null;
+            int i = 0;
+            components = new BandData[parser.resources.size()];
+            for (final Map.Entry<Band,String> entry : parser.resources.entrySet()) {
+                final Band band = entry.getKey();
+                final LocalName id = factory.createLocalName(scope, band.name());
+                components[i++] = new BandData(this, band, id, entry.getValue());
+            }
         } catch (IOException e) {
             throw new DataStoreException(e);
         } catch (FactoryException e) {
@@ -211,6 +251,23 @@ public class LandsatStore extends DataStore {
     }
 
     /**
+     * Returns the resources for each Landsat band.
+     *
+     * @return all bands that are components of this aggregate. Never {@code null}.
+     * @throws DataStoreException if an error occurred while fetching the components.
+     *
+     * @since 1.1
+     */
+    @Override
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    public synchronized List<GridCoverageResource> components() throws DataStoreException {
+        if (components == null) {
+            loadMetadata();
+        }
+        return UnmodifiableArrayList.wrap(components);
+    }
+
+    /**
      * Registers a listener to notify when the specified kind of event occurs in this data store.
      * The current implementation of this data store can emit only {@link WarningEvent}s;
      * any listener specified for another kind of events will be ignored.
@@ -231,5 +288,26 @@ public class LandsatStore extends DataStore {
     @Override
     public synchronized void close() throws DataStoreException {
         metadata = null;
+        final BandData[] bands = components;
+        if (bands != null) {
+            components = null;
+            DataStoreException error = null;
+            for (int i=0; i<bands.length; i++) {
+                final BandData band = bands[i];
+                if (band != null) {
+                    bands[i] = null;
+                    try {
+                        band.closeDataStore();
+                    } catch (DataStoreException e) {
+                        if (error == null) {
+                            error = e;
+                        } else {
+                            error.addSuppressed(e);
+                        }
+                    }
+                }
+            }
+            if (error != null) throw error;
+        }
     }
 }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
index bcbaa7a..ee3eb9a 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
@@ -30,6 +30,7 @@ import org.apache.sis.internal.geotiff.Predictor;
 import org.apache.sis.internal.geotiff.Compression;
 import org.apache.sis.internal.storage.TiledGridResource;
 import org.apache.sis.internal.storage.ResourceOnFileSystem;
+import org.apache.sis.internal.storage.StoreResource;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.math.Vector;
 
@@ -45,7 +46,7 @@ import org.apache.sis.math.Vector;
  * @since   1.1
  * @module
  */
-abstract class DataCube extends TiledGridResource implements ResourceOnFileSystem {
+abstract class DataCube extends TiledGridResource implements ResourceOnFileSystem, StoreResource {
     /**
      * The GeoTIFF reader which contain this {@code DataCube}.
      * Used for fetching information like the input channel and where to report warnings.
@@ -63,6 +64,14 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste
     }
 
     /**
+     * Returns the data store that produced this resource.
+     */
+    @Override
+    public final DataStore getOriginator() {
+        return reader.store;
+    }
+
+    /**
      * Returns the object on which to perform all synchronizations for thread-safety.
      */
     @Override
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 c73899c..7ce5bc1 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
@@ -1375,7 +1375,14 @@ final class ImageFileDirectory extends DataCube {
             referencing.completeMetadata(metadata);         // Must be after `getGridGeometry()`.
         }
         metadata.addTitleOrIdentifier(identifier.toString(), MetadataBuilder.Scope.RESOURCE);
-        final Metadata md = metadata.build(true);
+        /*
+         * Undocumented feature: if `GeoTiffStore` is not used as a standalone reader but is instead
+         * used as a component of a larger data store (e.g. Landsat), do not freeze the metadata.
+         * This is needed for allowing `LandsatStore` to modify those metadata before to return them
+         * to the user. We may revert this undocumented feature in the future if we provide a better
+         * way to perform metadata amendment.
+         */
+        final Metadata md = metadata.build(!reader.store.hidden);
         metadata = null;
         return md;
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java
new file mode 100644
index 0000000..fc42043
--- /dev/null
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java
@@ -0,0 +1,220 @@
+/*
+ * 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.storage;
+
+import java.util.List;
+import java.util.Optional;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.Query;
+import org.apache.sis.storage.event.StoreEvent;
+import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.Metadata;
+import org.opengis.util.GenericName;
+
+
+/**
+ * A grid coverage resource which is a wrapper around another grid coverage resource.
+ * Wrappers can be used for delaying data loading, modifying the identifier, completing metadata, <i>etc</i>.
+ * The wrapped resource is created only when first needed.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public abstract class GridResourceWrapper implements GridCoverageResource {
+    /**
+     * The coverage resource instance which provides the data.
+     * This is initially {@code null} and created when first needed.
+     */
+    private GridCoverageResource source;
+
+    /**
+     * Creates a new wrapper.
+     */
+    protected GridResourceWrapper() {
+    }
+
+    /**
+     * Creates the resource on which to delegate operations.
+     * This method is invoked in a synchronized block when first needed and the result is cached.
+     *
+     * @return the resource on which to delegate operations.
+     * @throws DataStoreException if the resource can not be created.
+     */
+    protected abstract GridCoverageResource createSource() throws DataStoreException;
+
+    /**
+     * Returns the potentially cached source.
+     * This method invokes {@link #createSource()} when first needed and caches the result.
+     *
+     * @return the resource on which to delegate operations.
+     * @throws DataStoreException if the resource can not be created.
+     */
+    protected final synchronized GridCoverageResource source() throws DataStoreException {
+        if (source == null) {
+            source = createSource();
+        }
+        return source;
+    }
+
+    /**
+     * Returns the resource persistent identifier.
+     * The default implementation delegates to the source.
+     *
+     * @return a persistent identifier unique within the data store, or absent if this resource has no such identifier.
+     * @throws DataStoreException if an error occurred while fetching the identifier.
+     */
+    @Override
+    public Optional<GenericName> getIdentifier() throws DataStoreException {
+        return source().getIdentifier();
+    }
+
+    /**
+     * Returns information about this resource.
+     * The default implementation delegates to the source.
+     *
+     * @return information about this resource. Should not be {@code null}.
+     * @throws DataStoreException if an error occurred while reading the metadata.
+     */
+    @Override
+    public Metadata getMetadata() throws DataStoreException {
+        return source().getMetadata();
+    }
+
+    /**
+     * Returns the spatiotemporal extent of this resource in its most natural coordinate reference system.
+     * This is not necessarily the smallest bounding box encompassing all data.
+     * The default implementation delegates to the source.
+     *
+     * @return the spatiotemporal resource extent. May be absent if none or too costly to compute.
+     * @throws DataStoreException if an error occurred while reading or computing the envelope.
+     */
+    @Override
+    public Optional<Envelope> getEnvelope() throws DataStoreException {
+        return source().getEnvelope();
+    }
+
+    /**
+     * Returns the valid extent of grid coordinates together with the conversion from those grid
+     * coordinates to real world coordinates. The default implementation delegates to the source.
+     *
+     * @return extent of grid coordinates together with their mapping to "real world" coordinates.
+     * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
+     */
+    @Override
+    public GridGeometry getGridGeometry() throws DataStoreException {
+        return source().getGridGeometry();
+    }
+
+    /**
+     * Returns the ranges of sample values together with the conversion from samples to real values.
+     * The default implementation delegates to the source.
+     *
+     * @return ranges of sample values together with their mapping to "real values".
+     * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
+     */
+    @Override
+    public List<SampleDimension> getSampleDimensions() throws DataStoreException {
+        return source().getSampleDimensions();
+    }
+
+    /**
+     * Loads a subset of the grid coverage represented by this resource.
+     * The default implementation delegates to the source.
+     *
+     * @param  domain  desired grid extent and resolution, or {@code null} for reading the whole domain.
+     * @param  range   0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
+     * @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, int... range) throws DataStoreException {
+        return source().read(domain, range);
+    }
+
+    /**
+     * Requests a subset of the coverage.
+     * The default implementation delegates to the source.
+     *
+     * @param  query  definition of domain (grid extent) and range (sample dimensions) filtering applied at reading time.
+     * @return resulting coverage resource (never {@code null}).
+     * @throws DataStoreException if an error occurred while processing the query.
+     */
+    @Override
+    public GridCoverageResource subset(final Query query) throws DataStoreException {
+        return source().subset(query);
+    }
+
+    /**
+     * Registers a listener to notify when the specified kind of event occurs in this resource or in children.
+     * The default implementation delegates to the source.
+     *
+     * @param  <T>        compile-time value of the {@code eventType} argument.
+     * @param  listener   listener to notify about events.
+     * @param  eventType  type of {@link StoreEvent} to listen (can not be {@code null}).
+     */
+    @Override
+    public <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) {
+        final GridCoverageResource source;
+        try {
+            source = source();
+        } catch (DataStoreException e) {
+            throw new BackingStoreException(e);
+        }
+        source.addListener(eventType, listener);
+    }
+
+    /**
+     * Unregisters a listener previously added to this resource for the given type of events.
+     * The default implementation delegates to the source.
+     *
+     * @param  <T>        compile-time value of the {@code eventType} argument.
+     * @param  listener   listener to stop notifying about events.
+     * @param  eventType  type of {@link StoreEvent} which were listened (can not be {@code null}).
+     */
+    @Override
+    public <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) {
+        final GridCoverageResource source;
+        synchronized (this) {
+            source = this.source;       // No need to invoke the `source()` method here.
+        }
+        if (source != null) {
+            source.removeListener(eventType, listener);
+        }
+    }
+
+    /**
+     * Closes the data store associated to the resource, then discards the resource.
+     * This method does not verify if the data store is still used by other resources.
+     *
+     * @throws DataStoreException if an error occurred while closing the data store.
+     */
+    public final void closeDataStore() throws DataStoreException {
+        final GridCoverageResource s = source;
+        source = null;
+        if (s instanceof StoreResource) {
+            ((StoreResource) s).getOriginator().close();
+        }
+    }
+}