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 2025/10/24 16:43:38 UTC

(sis) branch geoapi-4.0 updated (e2d7874240 -> a7ca5df178)

This is an automated email from the ASF dual-hosted git repository.

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


    from e2d7874240 If a TIFF `DateTime` tag exists, use it as the temporal coordinate of the grid geometry. This behavior is specified by the Defense Geospatial Information Working Group (DGIWG).
     new 2895777aa0 Add more test cases for `GridGeometry.getEnvelope(crs)`.
     new a7ca5df178 Move `MemoryFeatureSet` and `MemoryGridCoverage` to public API, after improvement in their API for consistency with each other.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/sis/coverage/grid/GridGeometryTest.java | 52 +++++++++++++
 .../MultiResolutionCoverageLoaderTest.java         |  2 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  6 +-
 .../org/apache/sis/storage/landsat/BandGroup.java  |  7 +-
 .../apache/sis/storage/landsat/LandsatStore.java   |  2 +-
 .../sis/storage/landsat/MetadataReaderTest.java    |  2 +-
 .../apache/sis/storage/netcdf/base/TestCase.java   |  2 +-
 .../org/apache/sis/storage/AbstractResource.java   | 32 +++-----
 .../sis/storage/{base => }/MemoryFeatureSet.java   | 64 ++++++++++++----
 ...source.java => MemoryGridCoverageResource.java} | 87 +++++++++++-----------
 .../main/org/apache/sis/storage/Resource.java      | 22 ++++--
 .../storage/aggregate/AggregatedFeatureSet.java    | 12 ---
 .../sis/storage/aggregate/AggregatedResource.java  | 10 +++
 .../aggregate/BandAggregateGridResource.java       | 36 ++++-----
 .../storage/aggregate/ConcatenatedFeatureSet.java  |  2 +-
 .../aggregate/ConcatenatedGridCoverage.java        |  6 +-
 .../aggregate/ConcatenatedGridResource.java        | 12 +--
 .../sis/storage/aggregate/CoverageAggregator.java  | 16 +++-
 .../apache/sis/storage/base/PseudoResource.java    | 72 ++++++++++++++++++
 .../apache/sis/storage/base/TiledGridResource.java | 14 ----
 .../storage/image/WritableSingleImageStore.java    |  4 +-
 .../main/org/apache/sis/storage/package-info.java  |  8 +-
 .../org/apache/sis/storage/CoverageSubsetTest.java |  4 +-
 .../test/org/apache/sis/storage/DataStoreMock.java |  2 +-
 .../org/apache/sis/storage/FeatureQueryTest.java   |  1 -
 .../org/apache/sis/storage/GridResourceMock.java   | 12 +--
 ...st.java => MemoryGridCoverageResourceTest.java} | 21 +++---
 .../aggregate/BandAggregateGridResourceTest.java   | 11 +--
 .../aggregate/ConcatenatedFeatureSetTest.java      |  2 +-
 .../sis/storage/aggregate/CoverageMosaicTest.java  |  5 +-
 .../sis/storage/aggregate/JoinFeatureSetTest.java  |  2 +-
 .../sis/storage/aggregate/OpaqueGridResource.java  |  6 +-
 .../test/org/apache/sis/map/SEPortrayerTest.java   | 17 +----
 .../sis/map/service/GraphicsPortrayerTest.java     |  2 +-
 .../coveragejson/CoverageJsonStoreTest.java        |  4 +-
 .../org/apache/sis/gui/dataset/RootResource.java   | 16 ----
 36 files changed, 350 insertions(+), 225 deletions(-)
 rename endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/{base => }/MemoryFeatureSet.java (58%)
 rename endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/{base/MemoryGridResource.java => MemoryGridCoverageResource.java} (79%)
 create mode 100644 endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PseudoResource.java
 rename endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/{base/MemoryGridResourceTest.java => MemoryGridCoverageResourceTest.java} (80%)


(sis) 01/02: Add more test cases for `GridGeometry.getEnvelope(crs)`.

Posted by de...@apache.org.
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 2895777aa07f22eff9459b2aba58019332278425
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Oct 24 14:28:35 2025 +0200

    Add more test cases for `GridGeometry.getEnvelope(crs)`.
---
 .../apache/sis/coverage/grid/GridGeometryTest.java | 52 ++++++++++++++++++++++
 .../sis/referencing/crs/DefaultCompoundCRS.java    |  6 +--
 2 files changed, 55 insertions(+), 3 deletions(-)

diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
index d3e7290e78..b5958df5c7 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -23,6 +23,7 @@ import org.opengis.referencing.crs.DerivedCRS;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.OperationNotFoundException;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.referencing.operation.matrix.Matrix2;
@@ -40,6 +41,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.referencing.operation.HardCodedConversions;
+import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.referencing.Assertions.assertMatrixEquals;
 import static org.apache.sis.referencing.Assertions.assertEnvelopeEquals;
 import static org.apache.sis.feature.Assertions.assertGridToCenterEquals;
@@ -498,23 +500,73 @@ public final class GridGeometryTest extends TestCase {
                     0,    0,     1)),
                 HardCodedCRS.WGS84);
 
+        // When the CRS is the same, envelope should be the same.
         Envelope envelope = grid.getEnvelope(HardCodedCRS.WGS84);
         assertSame(envelope, grid.getEnvelope());
         assertEnvelopeEquals(new GeneralEnvelope(
                 new double[] {-2, -7.5},
                 new double[] { 1, -3.0}), envelope);
 
+        // Swap axis order.
         envelope = grid.getEnvelope(HardCodedCRS.WGS84_LATITUDE_FIRST);
+        assertSame(envelope.getCoordinateReferenceSystem(), HardCodedCRS.WGS84_LATITUDE_FIRST);
         assertEnvelopeEquals(new GeneralEnvelope(
                 new double[] {-7.5, -2},
                 new double[] {-3.0,  1}), envelope);
 
+        // Map projection.
         envelope = grid.getEnvelope(HardCodedConversions.mercator());
         assertEnvelopeEquals(new GeneralEnvelope(
                 new double[] {-222638.98, -831717.36},
                 new double[] { 111319.49, -331876.53}), envelope, 0.01);
     }
 
+    /**
+     * Tests {@link GridGeometry#getEnvelope(CoordinateReferenceSystem)} when an axis is not of the expected type.
+     * The horizontal part of this test is the same as {@link #testGetEnvelope()} for making validation easier.
+     *
+     * @throws TransformException if coordinates cannot be transformed.
+     */
+    @Test
+    public void testGetEnvelopeWithMismatchedDimension() throws TransformException {
+        final var grid = new GridGeometry(
+                new GridExtent(null, null, new long[] {12, 18, 5}, false),
+                PixelInCell.CELL_CORNER,
+                MathTransforms.linear(new Matrix4(
+                    0.25, 0,    0, -2,
+                    0,   -0.25, 0, -3,
+                    0,    0,    9, 11,
+                    0,    0,    0,  1)),
+                HardCodedCRS.WGS84_WITH_TIME);
+
+        // When the CRS is the same, envelope should be the same.
+        Envelope envelope = grid.getEnvelope(HardCodedCRS.WGS84_WITH_TIME);
+        assertSame(envelope, grid.getEnvelope());
+        assertEnvelopeEquals(new GeneralEnvelope(
+                new double[] {-2, -7.5, 11},
+                new double[] { 1, -3.0, 56}), envelope);
+
+        // Cannot map time to gravity-related height.
+        Throwable e = assertThrows(TransformException.class, () -> grid.getEnvelope(HardCodedCRS.GEOID_3D)).getCause();
+        e = assertInstanceOf(OperationNotFoundException.class, e);
+        assertMessageContains(e, "WGS 84 + time", "WGS 84 + height");
+
+        // Ellipsoidal height is a special case, with default height of 0.
+        envelope = grid.getEnvelope(HardCodedCRS.WGS84_3D);
+        assertSame(envelope.getCoordinateReferenceSystem(), HardCodedCRS.WGS84_3D);
+        assertEnvelopeEquals(new GeneralEnvelope(
+                new double[] {-2, -7.5, 0},
+                new double[] { 1, -3.0, 0}), envelope);
+
+        // Same when working on `Envelope` instead of `GridExtent`.
+        final var withOnlyEnvelope = new GridGeometry(grid.getEnvelope());
+        envelope = withOnlyEnvelope.getEnvelope(HardCodedCRS.WGS84_3D);
+        assertSame(envelope.getCoordinateReferenceSystem(), HardCodedCRS.WGS84_3D);
+        assertEnvelopeEquals(new GeneralEnvelope(
+                new double[] {-2, -7.5, 0},
+                new double[] { 1, -3.0, 0}), envelope);
+    }
+
     /**
      * Tests {@link GridGeometry#upsample(long...)}.
      */
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index f659e9d9e0..8614c2b645 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -269,7 +269,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
     protected DefaultCompoundCRS(final CompoundCRS crs) {
         super(crs);
         if (crs instanceof DefaultCompoundCRS) {
-            final DefaultCompoundCRS that = (DefaultCompoundCRS) crs;
+            final var that = (DefaultCompoundCRS) crs;
             this.components = that.components;
             this.singles    = that.singles;
         } else {
@@ -371,7 +371,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
      * @see #getSingleComponents()
      */
     private boolean setSingleComponents(final List<? extends CoordinateReferenceSystem> elements) {
-        final List<SingleCRS> flattened = new ArrayList<>(elements.size());
+        final var flattened = new ArrayList<SingleCRS>(elements.size());
         final boolean identical = getSingleComponents(elements, flattened);
         singles = UnmodifiableArrayList.wrap(flattened.toArray(SingleCRS[]::new));
         return identical;
@@ -530,7 +530,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
             boolean changed = false;
             final boolean reorderCRS = convention.ordinal() >= AxesConvention.DISPLAY_ORIENTED.ordinal();
             final List<? extends CoordinateReferenceSystem> elements = reorderCRS ? singles : components;
-            final CoordinateReferenceSystem[] newComponents = new CoordinateReferenceSystem[elements.size()];
+            final var newComponents = new CoordinateReferenceSystem[elements.size()];
             for (int i=0; i<newComponents.length; i++) {
                 CoordinateReferenceSystem component = elements.get(i);
                 AbstractCRS m = castOrCopy(component);


(sis) 02/02: Move `MemoryFeatureSet` and `MemoryGridCoverage` to public API, after improvement in their API for consistency with each other.

Posted by de...@apache.org.
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 a7ca5df1780f533c9ef6a60fbb6626371daa7232
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Oct 24 18:42:38 2025 +0200

    Move `MemoryFeatureSet` and `MemoryGridCoverage` to public API,
    after improvement in their API for consistency with each other.
---
 .../MultiResolutionCoverageLoaderTest.java         |  2 +-
 .../org/apache/sis/storage/landsat/BandGroup.java  |  7 +-
 .../apache/sis/storage/landsat/LandsatStore.java   |  2 +-
 .../sis/storage/landsat/MetadataReaderTest.java    |  2 +-
 .../apache/sis/storage/netcdf/base/TestCase.java   |  2 +-
 .../org/apache/sis/storage/AbstractResource.java   | 32 +++-----
 .../sis/storage/{base => }/MemoryFeatureSet.java   | 64 ++++++++++++----
 ...source.java => MemoryGridCoverageResource.java} | 87 +++++++++++-----------
 .../main/org/apache/sis/storage/Resource.java      | 22 ++++--
 .../storage/aggregate/AggregatedFeatureSet.java    | 12 ---
 .../sis/storage/aggregate/AggregatedResource.java  | 10 +++
 .../aggregate/BandAggregateGridResource.java       | 36 ++++-----
 .../storage/aggregate/ConcatenatedFeatureSet.java  |  2 +-
 .../aggregate/ConcatenatedGridCoverage.java        |  6 +-
 .../aggregate/ConcatenatedGridResource.java        | 12 +--
 .../sis/storage/aggregate/CoverageAggregator.java  | 16 +++-
 .../apache/sis/storage/base/PseudoResource.java    | 72 ++++++++++++++++++
 .../apache/sis/storage/base/TiledGridResource.java | 14 ----
 .../storage/image/WritableSingleImageStore.java    |  4 +-
 .../main/org/apache/sis/storage/package-info.java  |  8 +-
 .../org/apache/sis/storage/CoverageSubsetTest.java |  4 +-
 .../test/org/apache/sis/storage/DataStoreMock.java |  2 +-
 .../org/apache/sis/storage/FeatureQueryTest.java   |  1 -
 .../org/apache/sis/storage/GridResourceMock.java   | 12 +--
 ...st.java => MemoryGridCoverageResourceTest.java} | 21 +++---
 .../aggregate/BandAggregateGridResourceTest.java   | 11 +--
 .../aggregate/ConcatenatedFeatureSetTest.java      |  2 +-
 .../sis/storage/aggregate/CoverageMosaicTest.java  |  5 +-
 .../sis/storage/aggregate/JoinFeatureSetTest.java  |  2 +-
 .../sis/storage/aggregate/OpaqueGridResource.java  |  6 +-
 .../test/org/apache/sis/map/SEPortrayerTest.java   | 17 +----
 .../sis/map/service/GraphicsPortrayerTest.java     |  2 +-
 .../coveragejson/CoverageJsonStoreTest.java        |  4 +-
 .../org/apache/sis/gui/dataset/RootResource.java   | 16 ----
 34 files changed, 295 insertions(+), 222 deletions(-)

diff --git a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
index a29b181882..2cfa93f0a4 100644
--- a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
+++ b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
@@ -101,7 +101,7 @@ public final class MultiResolutionCoverageLoaderTest extends TestCase {
     private static final class DummyResource extends AbstractGridCoverageResource {
         /** Creates a dummy resource. */
         DummyResource() {
-            super(null, false);
+            super(null);
         }
 
         /** Returns the preferred resolutions in units of CRS axes. */
diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/BandGroup.java b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/BandGroup.java
index 3e56317e93..9596970d84 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/BandGroup.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/BandGroup.java
@@ -31,7 +31,6 @@ import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.AbstractResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.base.StoreResource;
 import org.apache.sis.storage.base.MetadataBuilder;
 import org.apache.sis.util.ArraysExt;
@@ -69,10 +68,10 @@ final class BandGroup extends AbstractResource implements Aggregate, StoreResour
      * Creates a new aggregate for the specified group.
      * This constructor will copy only the resources for that group from the given array.
      */
-    private BandGroup(final StoreListeners parent, final BandGroupName group,
+    private BandGroup(final Resource parent, final BandGroupName group,
                       final Band[] resources, final int count)
     {
-        super(parent, false);
+        super(parent);
         this.group = group;
         int n = 0;
         Band[] components = new Band[resources.length];
@@ -88,7 +87,7 @@ final class BandGroup extends AbstractResource implements Aggregate, StoreResour
     /**
      * Creates aggregates for the given bands.
      */
-    static BandGroup[] group(final StoreListeners parent, final Band[] resources, final int count) {
+    static BandGroup[] group(final Resource parent, final Band[] resources, final int count) {
         final BandGroupName[] groups = BandGroupName.values();
         final BandGroup[] aggregates = new BandGroup[groups.length];
         int n = 0;
diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
index edd45d2353..aa82c8cea5 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/LandsatStore.java
@@ -251,7 +251,7 @@ public class LandsatStore extends DataStore implements Aggregate {
         } catch (FactoryException e) {
             throw new DataStoreReferencingException(e.getMessage(), e);
         }
-        final BandGroup[] bands = BandGroup.group(listeners, resources, count);
+        final BandGroup[] bands = BandGroup.group(this, resources, count);
         for (final BandGroup c : bands) {
             c.identifier = factory.createLocalName(scope, c.group.name());
         }
diff --git a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
index 31099f6d44..bc1b7c8544 100644
--- a/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
+++ b/endorsed/src/org.apache.sis.storage.earthobservation/test/org/apache/sis/storage/landsat/MetadataReaderTest.java
@@ -246,7 +246,7 @@ public final class MetadataReaderTest extends TestCase {
         final class DummyResource extends AbstractResource {
             /** Creates a dummy resource without parent. */
             DummyResource() {
-                super(null, false);
+                super(null);
             }
 
             /** Makes listeners accessible to this package. */
diff --git a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
index c34b3efeeb..cf34b35b18 100644
--- a/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
+++ b/endorsed/src/org.apache.sis.storage.netcdf/test/org/apache/sis/storage/netcdf/base/TestCase.java
@@ -138,7 +138,7 @@ public abstract class TestCase extends org.apache.sis.test.TestCase {
         final class DummyResource extends AbstractResource {
             /** Creates a dummy resource without parent. */
             DummyResource() {
-                super(null, false);
+                super(null);
             }
 
             /** Makes listeners accessible to this package. */
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractResource.java
index 49c9497819..d64b18401f 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/AbstractResource.java
@@ -18,7 +18,6 @@ package org.apache.sis.storage;
 
 import java.util.Locale;
 import java.util.Optional;
-import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.operation.TransformException;
@@ -31,6 +30,7 @@ import org.apache.sis.storage.event.StoreEvent;
 import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.internal.Resources;
+import org.apache.sis.storage.base.PseudoResource;
 import org.apache.sis.storage.base.MetadataBuilder;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
@@ -43,7 +43,7 @@ import org.apache.sis.xml.NilReason;
  * Subclasses should override the following methods:
  *
  * <ul>
- *   <li>{@link #getIdentifier()} (strongly recommended)</li>
+ *   <li>{@link #getIdentifier()} (recommended)</li>
  *   <li>{@link #getEnvelope()} (recommended)</li>
  *   <li>{@link #createMetadata()} (optional)</li>
  *   <li>{@link #getSynchronizationLock()} (optional)</li>
@@ -54,7 +54,7 @@ import org.apache.sis.xml.NilReason;
  * Synchronization, when needed, uses {@link #getSynchronizationLock()}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.6
  * @since   1.2
  */
 public abstract class AbstractResource implements Resource {
@@ -85,7 +85,14 @@ public abstract class AbstractResource implements Resource {
     @SuppressWarnings("this-escape")
     protected AbstractResource(final Resource parent) {
         StoreListeners parentListeners = null;
-        if (parent instanceof AbstractResource) {
+        if (parent instanceof PseudoResource) {     // Internal hack, may change in any future version.
+            final var r = (PseudoResource) parent;
+            parentListeners = r.listeners;
+            if (parentListeners != null && r.childrenAreHidden) {
+                listeners = parentListeners;
+                return;
+            }
+        } else if (parent instanceof AbstractResource) {
             parentListeners = ((AbstractResource) parent).listeners;
         } else if (parent instanceof DataStore) {
             parentListeners = ((DataStore) parent).listeners;
@@ -119,23 +126,6 @@ public abstract class AbstractResource implements Resource {
         }
     }
 
-    /**
-     * Returns the resource persistent identifier if available.
-     * The default implementation returns an empty value.
-     * Subclasses are strongly encouraged to override if they can provide a value.
-     *
-     * <h4>Relationship with metadata</h4>
-     * The default implementation of {@link #createMetadata()} uses this identifier for initializing
-     * the {@code metadata/identificationInfo/citation/title} property.
-     *
-     * @return a persistent identifier unique within the data store.
-     * @throws DataStoreException if an error occurred while fetching the identifier.
-     */
-    @Override
-    public Optional<GenericName> getIdentifier() throws DataStoreException {
-        return Optional.empty();
-    }
-
     /**
      * Returns the spatiotemporal envelope of this resource. This information is part of API only in some kinds
      * of resource such as {@link FeatureSet}. But the method is provided in this base class for convenience.
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/MemoryFeatureSet.java
similarity index 58%
rename from endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java
rename to endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/MemoryFeatureSet.java
index 8b25894a1b..a2d8297510 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/MemoryFeatureSet.java
@@ -14,14 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.storage.base;
+package org.apache.sis.storage;
 
 import java.util.Objects;
 import java.util.Collection;
 import java.util.OptionalLong;
 import java.util.stream.Stream;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.AbstractFeatureSet;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.feature.Feature;
@@ -29,36 +27,48 @@ import org.opengis.feature.FeatureType;
 
 
 /**
- * Set of features stored in memory. Features are specified at construction time.
- * Metadata can be specified by overriding {@link #createMetadata()}.
+ * Set of feature instances stored in memory.
+ * Features are specified at construction time.
+ * Metadata can be specified by overriding the {@link #createMetadata()} method.
+ *
+ * <h2>When to use</h2>
+ * This class is useful for small sets of features, or for testing purposes,
+ * or when the features are in memory anyway (for example, a computation result).
+ * It should generally not be used for large data sets read from files or databases.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @since 1.6
  */
 public class MemoryFeatureSet extends AbstractFeatureSet {
     /**
-     * The type specified at construction time and returned by {@link #getType()}.
+     * The type specified at construction time.
+     *
+     * @see #getType()
      */
-    private final FeatureType type;
+    protected final FeatureType type;
 
     /**
      * The features specified at construction time, potentially as a modifiable collection.
      * For all features in this collection, {@link Feature#getType()} shall be {@link #type}.
+     *
+     * @see #features(boolean)
      */
-    private final Collection<Feature> features;
+    protected final Collection<Feature> features;
 
     /**
      * Creates a new set of features stored in memory. It is caller responsibility to ensure that
      * <code>{@linkplain Feature#getType()} == type</code> for all elements in the given collection
-     * (this is not verified).
+     * (this is not verified by this constructor).
      *
-     * @param parent     the parent resource, or {@code null} if none.
-     * @param type       the type of all features in the given collection.
-     * @param features   collection of stored features. This collection will not be copied.
+     * @param parent    the parent resource, or {@code null} if none.
+     * @param type      the type of all features in the given collection.
+     * @param features  collection of stored features. This collection will not be copied.
      */
     public MemoryFeatureSet(final Resource parent, final FeatureType type, final Collection<Feature> features) {
         super(parent);
-        this.type     = Objects.requireNonNull(type);
+        this.type = Objects.requireNonNull(type);
         this.features = Objects.requireNonNull(features);
     }
 
@@ -92,4 +102,32 @@ public class MemoryFeatureSet extends AbstractFeatureSet {
     public Stream<Feature> features(final boolean parallel) {
         return parallel ? features.parallelStream() : features.stream();
     }
+
+    /**
+     * Tests whether this memory feature set is wrapping the same feature instances as the given object.
+     * This method checks also that the listeners are equal.
+     *
+     * @param  obj  the object to compare.
+     * @return whether the two objects are memory resources wrapping the same features.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj != null && obj.getClass() == getClass()) {
+            final var other = (MemoryFeatureSet) obj;
+            return type.equals(other.type) &&
+                   features.equals(other.features) &&
+                   listeners.equals(other.listeners);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for consistency with {@code equals(Object)}.
+     *
+     * @return a hash code value.
+     */
+    @Override
+    public int hashCode() {
+        return type.hashCode() + 31 * features.hashCode() + 37 * listeners.hashCode();
+    }
 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/MemoryGridCoverageResource.java
similarity index 79%
rename from endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryGridResource.java
rename to endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/MemoryGridCoverageResource.java
index dc4e70c0f2..3d0ed813a4 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MemoryGridResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/MemoryGridCoverageResource.java
@@ -14,14 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.storage.base;
+package org.apache.sis.storage;
 
 import java.util.List;
 import java.util.Arrays;
 import java.util.Objects;
-import java.util.Optional;
 import java.awt.image.RenderedImage;
-import org.opengis.util.GenericName;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridCoverageBuilder;
@@ -30,36 +28,41 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridRoundingMode;
 import org.apache.sis.coverage.grid.PixelInCell;
-import org.apache.sis.storage.AbstractGridCoverageResource;
-import org.apache.sis.storage.RasterLoadingStrategy;
-import org.apache.sis.storage.event.StoreListeners;
-import static org.apache.sis.storage.base.TiledGridCoverage.BIDIMENSIONAL;
 
 
 /**
- * A {@link org.apache.sis.storage.GridCoverageResource} in memory.
+ * A Grid Coverage Resource stored in memory.
  * This resource wraps an arbitrary {@link GridCoverage} specified at construction time.
- * Metadata can be specified by overriding {@link #createMetadata()}.
+ * Metadata can be specified by overriding the {@link #createMetadata()} method.
+ *
+ * <h2>When to use</h2>
+ * This class is useful for small grid coverages, or for testing purposes,
+ * or when the coverage is in memory anyway (for example, a computation result).
+ * It should generally not be used for large coverages read from files or databases.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @since 1.6
  */
-public class MemoryGridResource extends AbstractGridCoverageResource {
+public class MemoryGridCoverageResource extends AbstractGridCoverageResource {
     /**
-     * The grid coverage specified at construction time.
+     * A constant for identifying code that relying on having 2 dimensions.
      */
-    public final GridCoverage coverage;
+    private static final int BIDIMENSIONAL = 2;
 
     /**
-     * The grid coverage processor to use for selecting bands.
-     * It may be configured with a colorizer for determining the color models.
+     * The grid coverage specified at construction time.
+     *
+     * @see #getGridCoverage()
      */
-    private final GridCoverageProcessor processor;
+    protected final GridCoverage coverage;
 
     /**
-     * The resource identifier, or {@code null} if none.
+     * The grid coverage processor to use for selecting bands.
+     * It may be configured with a colorizer for determining the color models.
      */
-    private final GenericName identifer;
+    protected final GridCoverageProcessor processor;
 
     /**
      * Whether to defer the calls to {@link GridCoverage#render(GridExtent)}.
@@ -71,26 +74,28 @@ public class MemoryGridResource extends AbstractGridCoverageResource {
     /**
      * Creates a new coverage stored in memory.
      *
-     * @param  parent      listeners of the parent resource, or {@code null} if none.
-     * @param  identifier  resource identifier, or {@code null} if none.
-     * @param  coverage    stored coverage retained as-is (not copied). Cannot be null.
-     * @param  processor   the grid coverage processor for selecting bands, or {@code null} for default.
+     * @param parent     the parent resource, or {@code null} if none.
+     * @param coverage   stored coverage retained as-is (not copied). Cannot be null.
+     * @param processor  the grid coverage processor for selecting bands, or {@code null} for default.
      */
-    public MemoryGridResource(final StoreListeners parent, final GenericName identifier,
-                              final GridCoverage coverage, final GridCoverageProcessor processor)
-    {
-        super(parent, false);
-        this.identifer = identifier;
+    public MemoryGridCoverageResource(final Resource parent, final GridCoverage coverage, final GridCoverageProcessor processor) {
+        super(parent);
         this.coverage  = Objects.requireNonNull(coverage);
         this.processor = (processor != null) ? processor : new GridCoverageProcessor();
     }
 
     /**
-     * Returns the resource identifier specified at construction time, if any.
+     * Returns the grid coverage wrapped by this resource.
+     * The grid coverage returned by this method shall be the same or equivalent
+     * to the coverage that would be returned by a call to {@code read(null)},
+     * and should not be expansive to get.
+     *
+     * @return the grid coverage wrapped by this resource.
+     *
+     * @see #read(GridGeometry, int...)
      */
-    @Override
-    public Optional<GenericName> getIdentifier() {
-        return Optional.ofNullable(identifer);
+    public GridCoverage getGridCoverage() {
+        return coverage;
     }
 
     /**
@@ -132,10 +137,10 @@ public class MemoryGridResource extends AbstractGridCoverageResource {
     }
 
     /**
-     * Returns a subset of the wrapped grid coverage. If a non-null grid geometry is specified, then
-     * this method tries to return a grid coverage matching the given grid geometry on a best-effort basis.
-     * In the current implementation, this is either a {@link org.apache.sis.coverage.grid.GridCoverage2D}
-     * or the original grid coverage.
+     * Returns a subset of the wrapped grid coverage. If a null grid geometry and a null or empty range is specified,
+     * then this method shall return the same grid coverage as {@link #getGridCoverage()} or an equivalent coverage.
+     * If a non-null grid geometry is specified, then this method tries to return a grid coverage matching the given
+     * grid geometry on a best-effort basis. It may be the whole coverage.
      *
      * @param  domain  desired grid extent and resolution, or {@code null} for the whole domain.
      * @param  ranges  0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
@@ -228,8 +233,8 @@ public class MemoryGridResource extends AbstractGridCoverageResource {
     }
 
     /**
-     * Tests whether this memory grid resource is wrapping the same coverage than the given object.
-     * This method requires also the listeners and processor to be the equal.
+     * Tests whether this memory grid coverage resource is wrapping the same coverage as the given object.
+     * This method checks also that the listeners and the grid coverage processor are equal.
      *
      * @param  obj  the object to compare.
      * @return whether the two objects are memory resources wrapping the same coverage.
@@ -237,9 +242,8 @@ public class MemoryGridResource extends AbstractGridCoverageResource {
     @Override
     public boolean equals(final Object obj) {
         if (obj != null && obj.getClass() == getClass()) {
-            final var other = (MemoryGridResource) obj;
-            return Objects.equals(identifer, other.identifer) &&
-                   coverage.equals(other.coverage)   &&
+            final var other = (MemoryGridCoverageResource) obj;
+            return coverage.equals(other.coverage)   &&
                    processor.equals(other.processor) &&
                    listeners.equals(other.listeners);
         }
@@ -253,10 +257,7 @@ public class MemoryGridResource extends AbstractGridCoverageResource {
      */
     @Override
     public int hashCode() {
-        return coverage.hashCode()
-                + 17 * Objects.hashCode(identifer)
-                + 31 * processor.hashCode()
-                + 37 * listeners.hashCode();
+        return coverage.hashCode() + 31 * processor.hashCode() + 37 * listeners.hashCode();
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java
index 61022718dc..34f16f3acc 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java
@@ -54,7 +54,7 @@ import org.apache.sis.storage.event.StoreListener;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
  *
  * @see Aggregate#components()
  *
@@ -84,7 +84,9 @@ public interface Resource {
      *
      * @since 1.0
      */
-    Optional<GenericName> getIdentifier() throws DataStoreException;
+    default Optional<GenericName> getIdentifier() throws DataStoreException {
+        return Optional.empty();
+    }
 
     /**
      * Returns information about this resource.
@@ -140,7 +142,7 @@ public interface Resource {
      * If the caller wants to modify some metadata elements, it may be necessary to perform a
      * {@linkplain org.apache.sis.metadata.iso.DefaultMetadata#deepCopy(Metadata) deep copy} first.
      *
-     * @return information about this resource. Should not be {@code null}.
+     * @return information about this resource. Should not be {@code null}, but this is not strictly forbidden.
      * @throws DataStoreException if an error occurred while reading the metadata.
      *
      * @see DataStore#getMetadata()
@@ -364,11 +366,16 @@ public interface Resource {
      * This side-effect is applied on the assumption that the registered listener will handle
      * warnings in its own way, for example by showing warnings in a widget.
      *
+     * <h4>Default implementation</h4>
+     * The default implementation does nothing.
+     * This is okay for resources that do not emit any event.
+     *
      * @param  <T>        compile-time value of the {@code eventType} argument.
      * @param  listener   listener to notify about events.
      * @param  eventType  type of {@link StoreEvent}s to listen (cannot be {@code null}).
      */
-    <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener);
+    default <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) {
+    }
 
     /**
      * Unregisters a listener previously added to this resource for the given type of events.
@@ -386,9 +393,14 @@ public interface Resource {
      * and if, after this method invocation, there are no remaining listeners for warning events,
      * then this {@code Resource} will send future warnings to the loggers.
      *
+     * <h4>Default implementation</h4>
+     * The default implementation does nothing.
+     * This is okay for resources that do not emit any event.
+     *
      * @param  <T>        compile-time value of the {@code eventType} argument.
      * @param  listener   listener to stop notifying about events.
      * @param  eventType  type of {@link StoreEvent}s which were listened (cannot be {@code null}).
      */
-    <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener);
+    default <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) {
+    }
 }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java
index 63b180e995..6a7e1f17b2 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedFeatureSet.java
@@ -30,7 +30,6 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.AbstractFeatureSet;
-import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.base.MetadataBuilder;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -70,17 +69,6 @@ abstract class AggregatedFeatureSet extends AbstractFeatureSet {
         super(parent);
     }
 
-    /**
-     * Creates a new aggregated feature set.
-     *
-     * @param  parent  listeners of the parent resource, or {@code null} if none.
-     *         This is usually the listeners of the {@link org.apache.sis.storage.DataStore}
-     *         that created this resource.
-     */
-    protected AggregatedFeatureSet(final StoreListeners parent) {
-        super(parent, false);
-    }
-
     /**
      * Returns all feature set used by this aggregation. This method is invoked for implementation of
      * {@link #getEnvelope()} and {@link #createMetadata(MetadataBuilder)}.
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedResource.java
index 4930ea9f4f..b037d547a7 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/AggregatedResource.java
@@ -28,6 +28,7 @@ import org.apache.sis.storage.AbstractResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.storage.base.MetadataBuilder;
+import org.apache.sis.storage.base.PseudoResource;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.util.internal.shared.Strings;
@@ -73,6 +74,15 @@ abstract class AggregatedResource extends AbstractResource {
      */
     private boolean envelopeIsEvaluated;
 
+    /**
+     * Creates a new concatenated resource as the child of the given resource.
+     *
+     * @param  parent  the parent resource, or {@code null} if none.
+     */
+    AggregatedResource(final PseudoResource parent) {
+        super(parent);
+    }
+
     /**
      * Creates a new concatenated resource.
      *
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/BandAggregateGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
index ad3fd98827..40d122fdf3 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
@@ -30,9 +30,9 @@ import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.AbstractGridCoverageResource;
 import org.apache.sis.storage.RasterLoadingStrategy;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 import org.apache.sis.storage.base.MetadataBuilder;
-import org.apache.sis.storage.base.MemoryGridResource;
-import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.base.PseudoResource;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.internal.shared.UnmodifiableArrayList;
@@ -109,18 +109,18 @@ final class BandAggregateGridResource extends AggregatedResource implements Grid
      * The {@linkplain #getSampleDimensions() list of sample dimensions} of the aggregated resource
      * will be the concatenation of the lists of all sources, or a subset of this concatenation.
      *
-     * @param  parentListeners  listeners of the parent resource, or {@code null} if none.
-     * @param  aggregate        sources to aggregate together with the bands to select for each source.
-     * @param  processor        the processor to use for creating grid coverages.
+     * @param  parent     the parent resource, or {@code null} if none.
+     * @param  aggregate  sources to aggregate together with the bands to select for each source.
+     * @param  processor  the processor to use for creating grid coverages.
      * @throws BackingStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
      * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
      * @throws IllegalArgumentException if some band indices are duplicated or outside their range of validity.
      */
-    private BandAggregateGridResource(final StoreListeners parentListeners,
+    private BandAggregateGridResource(final PseudoResource parent,
                                       final BandAggregateArgument<GridCoverageResource> aggregate,
                                       final GridCoverageProcessor processor)
     {
-        super(null, parentListeners, false);
+        super(parent);
         this.sources          = aggregate.sources();
         this.gridGeometry     = aggregate.domain(BandAggregateGridResource::domain);
         this.sampleDimensions = List.copyOf(aggregate.ranges());
@@ -149,16 +149,16 @@ final class BandAggregateGridResource extends AggregatedResource implements Grid
      * The intersection of the domain of all resources shall be non-empty,
      * and all resources shall use the same data type in their rendered image.
      *
-     * @param  parentListeners  listeners of the parent resource, or {@code null} if none.
-     * @param  sources          resources whose bands shall be aggregated, in order. At least one resource must be provided.
-     * @param  bandsPerSource   sample dimensions for each source. May be {@code null} or may contain {@code null} elements.
-     * @param  processor        the processor to use for creating grid coverages.
+     * @param  parent          the parent resource, or {@code null} if none.
+     * @param  sources         resources whose bands shall be aggregated, in order. At least one resource must be provided.
+     * @param  bandsPerSource  sample dimensions for each source. May be {@code null} or may contain {@code null} elements.
+     * @param  processor       the processor to use for creating grid coverages.
      * @return the band aggregated grid resource.
      * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
      * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
      * @throws IllegalArgumentException if some band indices are duplicated or outside their range of validity.
      */
-    static GridCoverageResource create(final StoreListeners parentListeners,
+    static GridCoverageResource create(final PseudoResource parent,
             GridCoverageResource[] sources, int[][] bandsPerSource,
             final GridCoverageProcessor processor) throws DataStoreException
     {
@@ -168,14 +168,14 @@ final class BandAggregateGridResource extends AggregatedResource implements Grid
         for (int i=0; i<sources.length; i++) {
             final GridCoverageResource source = sources[i];
             ArgumentChecks.ensureNonNullElement("sources", i, source);
-            if (source instanceof MemoryGridResource) {
+            if (source instanceof MemoryGridCoverageResource) {
                 if (count == 0) {
                     sources = sources.clone();              // Clone when first needed.
                     bandsPerSource = (bandsPerSource != null) ? bandsPerSource.clone() : new int[sources.length][];
                 }
                 final int[] bands    = bandsPerSource[i];
                 final int numBands   = (bands != null) ? bands.length : source.getSampleDimensions().size();
-                coverages[count]     = ((MemoryGridResource) source).coverage;
+                coverages[count]     = ((MemoryGridCoverageResource) source).getGridCoverage();
                 coverageBands[count] = bands;
                 bandsPerSource[i]    = ArraysExt.range(firstBand, firstBand + numBands);
                 sources[i]           = null;        // To be replaced by the aggregated coverage.
@@ -184,13 +184,13 @@ final class BandAggregateGridResource extends AggregatedResource implements Grid
             }
         }
         /*
-         * If at least one `MemoryGridResource` has been found, apply the aggregation directly
-         * on the grid coverage, then build a single `MemoryGridResource` with the result.
+         * If at least one `MemoryGridCoverageResource` has been found, apply the aggregation directly
+         * on the grid coverage, then build a single `MemoryGridCoverageResource` with the result.
          */
         if (count != 0) {
             coverages     = ArraysExt.resize(coverages,     count);
             coverageBands = ArraysExt.resize(coverageBands, count);
-            var aggregate = new MemoryGridResource(parentListeners, null, processor.aggregateRanges(coverages, coverageBands), processor);
+            var aggregate = new MemoryGridCoverageResource(parent, processor.aggregateRanges(coverages, coverageBands), processor);
             for (int i=0; i<sources.length; i++) {
                 if (sources[i] == null) {
                     sources[i] = aggregate;
@@ -209,7 +209,7 @@ final class BandAggregateGridResource extends AggregatedResource implements Grid
             if (aggregate.isIdentity()) {
                 return aggregate.sources()[0];
             }
-            return new BandAggregateGridResource(parentListeners, aggregate, processor);
+            return new BandAggregateGridResource(parent, aggregate, processor);
         } catch (BackingStoreException e) {
             throw e.unwrapOrRethrow(DataStoreException.class);
         }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
index dbafaf4e76..78a9582e76 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
@@ -76,7 +76,7 @@ public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
      * but different sources. This is used for creating {@linkplain #subset(Query) subsets}.
      */
     private ConcatenatedFeatureSet(final FeatureSet[] sources, final ConcatenatedFeatureSet original) {
-        super(original.listeners);
+        super(original);
         this.sources = UnmodifiableArrayList.wrap(sources);
         commonType = original.commonType;
     }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
index 9649c38087..743bbf6863 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
@@ -28,9 +28,9 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.DisjointExtentException;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.internal.Resources;
-import org.apache.sis.storage.base.MemoryGridResource;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.internal.shared.Numerics;
@@ -135,8 +135,8 @@ final class ConcatenatedGridCoverage extends GridCoverage {
         if (opposite == null) {
             List<SampleDimension> sampleDimensions = null;
             for (final GridSlice slice : locator.slices) {
-                if (slice.resource instanceof MemoryGridResource) {
-                    GridCoverage coverage = ((MemoryGridResource) slice.resource).coverage;
+                if (slice.resource instanceof MemoryGridCoverageResource) {
+                    GridCoverage coverage = ((MemoryGridCoverageResource) slice.resource).getGridCoverage();
                     coverage = coverage.forConvertedValues(converted);
                     sampleDimensions = coverage.getSampleDimensions();
                     break;
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
index 760d194e16..b7e01be507 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
@@ -34,9 +34,9 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreReferencingException;
 import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 import org.apache.sis.storage.RasterLoadingStrategy;
 import org.apache.sis.storage.event.StoreListeners;
-import org.apache.sis.storage.base.MemoryGridResource;
 import org.apache.sis.storage.base.MetadataBuilder;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ComparisonMode;
@@ -72,7 +72,7 @@ final class ConcatenatedGridResource extends AggregatedResource implements GridC
      * <p>Whether a bit is set or not depends on three factors:</p>
      * <ul>
      *   <li>Whether deferred loading has been requested by a call to {@link #setLoadingStrategy(RasterLoadingStrategy)}.</li>
-     *   <li>Whether the resource is a {@link MemoryGridResource} instance, in which case it is not useful to delay.</li>
+     *   <li>Whether the resource is a {@link MemoryGridCoverageResource} instance, in which case it is not useful to delay.</li>
      *   <li>Whether the slice at the corresponding index can handle deferred loading itself.
      *       In such case, we let the resource manages its own lazy loading.</li>
      * </ul>
@@ -335,12 +335,12 @@ final class ConcatenatedGridResource extends AggregatedResource implements GridC
 
     /**
      * Returns whether the given slice should be ignored in the determination of a loading strategy.
-     * {@link MemoryGridResource} instances are ignored because they usually behave like "at get tile time",
+     * {@link MemoryGridCoverageResource} instances are ignored because they usually behave like "at get tile time",
      * even if their {@code setLoadingStrategy(…)} method rejects the value (because it cannot be guaranteed).
      * We also don't flag those instances as deferred for the same reason.
      */
     private static boolean ignore(final GridCoverageResource slice) {
-        return slice instanceof MemoryGridResource;
+        return slice instanceof MemoryGridCoverageResource;
     }
 
     /**
@@ -473,7 +473,7 @@ final class ConcatenatedGridResource extends AggregatedResource implements GridC
          * For all coverages that have been read immediately, wrap them in a memory resource.
          * This look cannot be inlined inside above loop because we need to know the adjusted domain.
          * This loop has some tolerance regarding coverages with a resolution different than expected.
-         * This is okay if `MemoryGridResource` applies the missing subsampling in its `read(…)` method.
+         * This is okay if `MemoryGridCoverageResource` applies the missing subsampling in its `read(…)` method.
          */
         if (coverages != null) try {
             for (int i=0; i < selected.length; i++) {
@@ -481,7 +481,7 @@ final class ConcatenatedGridResource extends AggregatedResource implements GridC
                 if (coverage != null) {
                     GridGeometry geometry = coverage.getGridGeometry();
                     GridExtent inGroup = domain.extentOf(geometry, GridSlice.CELL_ANCHOR, GridRoundingMode.NEAREST);
-                    var resource = new MemoryGridResource(listeners, null, coverage, processor);
+                    var resource = new MemoryGridCoverageResource(this, coverage, processor);
                     selected[i] = selected[i].resolve(resource, geometry.getExtent(), inGroup);
                 }
             }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
index 5de1f71862..92c3203e0a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
@@ -44,7 +44,8 @@ import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.GridCoverageResource;
-import org.apache.sis.storage.base.MemoryGridResource;
+import org.apache.sis.storage.MemoryGridCoverageResource;
+import org.apache.sis.storage.base.PseudoResource;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.SubspaceNotSpecifiedException;
@@ -188,6 +189,13 @@ public final class CoverageAggregator extends Group<GroupBySample> {
         return (listeners != null) ? listeners.getSourceName() : null;
     }
 
+    /**
+     * Returns this aggregator as a pseudo-resource, used only for passing the listeners.
+     */
+    private PseudoResource asPseudoResource() {
+        return new PseudoResource(listeners, false);
+    }
+
     /**
      * Adds the given coverage. This method can be invoked from any thread.
      *
@@ -197,10 +205,10 @@ public final class CoverageAggregator extends Group<GroupBySample> {
      */
     public void add(final GridCoverage coverage) {
         try {
-            add(new MemoryGridResource(listeners, null, coverage, processor));
+            add(new MemoryGridCoverageResource(asPseudoResource(), coverage, processor));
         } catch (DataStoreException e) {
             /*
-             * `DataStoreException` are never thrown by `MemoryGridResource`.
+             * `DataStoreException` are never thrown by `MemoryGridCoverageResource`.
              * The only case where we could get that exception with default
              * `add(GridCoverageResource)` is with non-invertible transform.
              */
@@ -520,7 +528,7 @@ search: synchronized (members) {
      */
     public void addRangeAggregate(final GridCoverageResource[] sources, final int[][] bandsPerSource) throws DataStoreException {
         if (sources.length != 0) {
-            add(BandAggregateGridResource.create(listeners, sources, bandsPerSource, processor));
+            add(BandAggregateGridResource.create(asPseudoResource(), sources, bandsPerSource, processor));
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PseudoResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PseudoResource.java
new file mode 100644
index 0000000000..9d14b40547
--- /dev/null
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PseudoResource.java
@@ -0,0 +1,72 @@
+/*
+ * 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.base;
+
+import org.apache.sis.storage.Resource;
+import org.apache.sis.storage.AbstractResource;
+import org.apache.sis.storage.event.StoreListeners;
+import org.opengis.metadata.Metadata;
+
+
+/**
+ * A pseudo-resource used as a way to specify listeners to the {@link AbstractResource} constructor.
+ * Instances of this class are short-lived: no reference should be stored.
+ * This pseudo-resource should never be visible to users.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class PseudoResource implements Resource {
+    /**
+     * The listeners that {@link AbstractResource} should take. May be {@code null}.
+     */
+    public final StoreListeners listeners;
+
+    /**
+     * Whether the children of this pseudo-resource should be hidden.
+     * If {@code false} (the recommended value), then the children will have their own set of listeners
+     * and each child will be the {@linkplain StoreListeners#getSource() source of their own events}.
+     * It will be possible to add and remove listeners independently from the set of parent listeners.
+     * Conversely if {@code true}, then the {@linkplain #listeners} will be used directly and the
+     * children resource will not appear as the source of any event.
+     *
+     * <p>In any cases, the listeners of all parents (ultimately the data store that created the resource)
+     * will always be notified, either directly if {@code childrenAreHidden} is {@code true} or indirectly
+     * if {@code childrenAreHidden} is {@code false}.</p>
+     */
+    public final boolean childrenAreHidden;
+
+    /**
+     * Creates a new instance wrapping the given resources.
+     *
+     * @param  listeners          the listeners that {@link AbstractResource} should take, or {@code null}.
+     * @param  childrenAreHidden  whether the children of this pseudo-resource should be hidden.
+     */
+    public PseudoResource(final StoreListeners listeners, final boolean childrenAreHidden) {
+        this.listeners = listeners;
+        this.childrenAreHidden = childrenAreHidden;
+    }
+
+    /**
+     * Ignored.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public Metadata getMetadata() {
+        return null;
+    }
+}
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java
index 145daf7ae5..d2442b62cc 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java
@@ -45,7 +45,6 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.AbstractGridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.RasterLoadingStrategy;
-import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.internal.shared.Numerics;
@@ -158,19 +157,6 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource {
         super(parent);
     }
 
-    /**
-     * Creates a new resource.
-     *
-     * @param  parent  listeners of the parent resource, or {@code null} if none.
-     *         This is usually the listeners of the {@link org.apache.sis.storage.DataStore}
-     *         that created this resource.
-     * @param  hidden  {@code false} if this resource shall use its own {@link StoreListeners}
-     *         with the specified parent, or {@code true} for using {@code parentListeners} directly.
-     */
-    protected TiledGridResource(final StoreListeners parent, final boolean hidden) {
-        super(parent, hidden);
-    }
-
     /**
      * Returns the size of tiles in this resource.
      * The length of the returned array is the number of dimensions,
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableSingleImageStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableSingleImageStore.java
index 3c00dfd441..fa05ffb012 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableSingleImageStore.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableSingleImageStore.java
@@ -25,11 +25,11 @@ 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.MemoryGridCoverageResource;
 import org.apache.sis.storage.RasterLoadingStrategy;
 import org.apache.sis.storage.UnsupportedQueryException;
 import org.apache.sis.storage.WritableGridCoverageResource;
 import org.apache.sis.storage.Query;
-import org.apache.sis.storage.base.MemoryGridResource;
 
 
 /**
@@ -168,7 +168,7 @@ final class WritableSingleImageStore extends WritableStore implements WritableGr
     public void write(final GridCoverage coverage, final Option... options) throws DataStoreException {
         try {
             if (isMultiImages() == 0) {
-                add(new MemoryGridResource(listeners, null, coverage, null));
+                add(new MemoryGridCoverageResource(this, coverage, null));
             } else {
                 delegate().write(coverage, options);
             }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/package-info.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/package-info.java
index acb177d843..4a846c5c9d 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/package-info.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/package-info.java
@@ -16,13 +16,11 @@
  */
 
 /**
- * {@linkplain org.apache.sis.storage.DataStore Data store} base types for retrieving and saving geospatial data
- * in various storage formats.
- *
- * <p>{@code DataStore} provides the methods for reading or writing geospatial data in a given storage.
+ * Data store base types for retrieving and saving geospatial data in various storage formats.
+ * {@link org.apache.sis.storage.DataStore} provides the methods for reading or writing geospatial data in a given storage.
  * A storage may be a file, a directory, a connection to a database or any other implementation specific mechanism.
  * Suitable {@code DataStore} implementation for a given storage can be discovered and opened by the static methods
- * provided in {@link org.apache.sis.storage.DataStores}.</p>
+ * provided in {@link org.apache.sis.storage.DataStores}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/CoverageSubsetTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/CoverageSubsetTest.java
index 833577cd04..2d1fad781c 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/CoverageSubsetTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/CoverageSubsetTest.java
@@ -28,7 +28,6 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridOrientation;
 import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.storage.base.MemoryGridResource;
 import org.apache.sis.measure.Units;
 
 // Test dependencies
@@ -46,6 +45,7 @@ import static org.apache.sis.referencing.Assertions.assertEquivalent;
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class CoverageSubsetTest extends TestCase {
     /**
      * Size (in pixels) of the image to build for testing purpose.
@@ -70,7 +70,7 @@ public final class CoverageSubsetTest extends TestCase {
         final var domain = new GridGeometry(extent, region, GridOrientation.HOMOTHETY);
         final var band   = new SampleDimension.Builder().addQuantitative("101-based row-major order pixel number", 101, 105, 1, 0, Units.UNITY).build();
         final var buffer = new DataBufferInt(values(), WIDTH * HEIGHT);
-        return new MemoryGridResource(null, null, new BufferedGridCoverage(domain, List.of(band), buffer), null);
+        return new MemoryGridCoverageResource(null, new BufferedGridCoverage(domain, List.of(band), buffer), null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/DataStoreMock.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/DataStoreMock.java
index 90d6ee2975..86c581c180 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/DataStoreMock.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/DataStoreMock.java
@@ -100,7 +100,7 @@ public final class DataStoreMock extends DataStore {
      * @return a dummy child.
      */
     public Resource newChild() {
-        return new AbstractResource(listeners, false) {
+        return new AbstractResource(this) {
         };
     }
 }
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
index ac13d60d71..4642c8380d 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
@@ -24,7 +24,6 @@ import org.apache.sis.feature.Features;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.builder.AttributeRole;
 import org.apache.sis.feature.internal.shared.AttributeConvention;
-import org.apache.sis.storage.base.MemoryFeatureSet;
 import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.util.iso.Names;
 
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/GridResourceMock.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/GridResourceMock.java
index 306bcea036..7e33359497 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/GridResourceMock.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/GridResourceMock.java
@@ -23,6 +23,7 @@ import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridCoverage2D;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.*;
@@ -30,11 +31,10 @@ import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * A resource for one-banded images generated on-the-fly when {@link #read(GridGeometry, int...)} is invoked.
- * This class has some similarities with {@link org.apache.sis.storage.base.MemoryGridResource} except that
- * the {@link GridCoverage} returned by {@link #read read(…)} is guaranteed to wrap an image having exactly the
- * requested size (i.e. the size specified by {@link GridGeometry#getExtent()}).
- * By contrast {@code org.apache.sis.storage.base.MemoryGridResource} may return images larger than requested,
- * which make testing more difficult.
+ * This class has some similarities with {@link MemoryGridCoverageResource} except that the {@link GridCoverage}
+ * returned by the {@link #read read(…)} method is guaranteed to wrap an image having exactly the requested size
+ * (i.e. the size specified by {@link GridGeometry#getExtent()}). By contrast, {@link MemoryGridCoverageResource}
+ * may return images larger than requested, which make testing more difficult.
  *
  * @author  Johann Sorel (Geomatys)
  */
@@ -53,7 +53,7 @@ final class GridResourceMock extends AbstractGridCoverageResource {
      * Creates a resource mock with the given grid geometry.
      */
     GridResourceMock(final GridGeometry gridGeometry) {
-        super(null, false);
+        super(null);
         assertNotNull(gridGeometry);
         this.gridGeometry     = gridGeometry;
         this.sampleDimensions = List.of(new SampleDimension.Builder().setName(0).build());
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MemoryGridResourceTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/MemoryGridCoverageResourceTest.java
similarity index 80%
rename from endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MemoryGridResourceTest.java
rename to endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/MemoryGridCoverageResourceTest.java
index ef0750a4ae..eb59bca38f 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MemoryGridResourceTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/MemoryGridCoverageResourceTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.storage.base;
+package org.apache.sis.storage;
 
 import java.awt.image.BufferedImage;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -34,11 +34,12 @@ import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
 
 
 /**
- * Tests {@link MemoryGridResource}.
+ * Tests {@link MemoryGridCoverageResource}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class MemoryGridResourceTest extends TestCase {
+@SuppressWarnings("exports")
+public final class MemoryGridCoverageResourceTest extends TestCase {
     /**
      * Arbitrary size for the grid to test.
      */
@@ -57,24 +58,24 @@ public final class MemoryGridResourceTest extends TestCase {
     /**
      * The resource to be tested by each test method.
      */
-    private final MemoryGridResource resource;
+    private final MemoryGridCoverageResource resource;
 
     /**
      * Creates a new test case.
      */
-    public MemoryGridResourceTest() {
+    public MemoryGridCoverageResourceTest() {
         crs = HardCodedCRS.WGS84;
         gridToCRS = new AffineTransform2D(2, 0, 0, 3, 0, 0);
-        final GridGeometry grid = new GridGeometry(new GridExtent(WIDTH, HEIGHT), PixelInCell.CELL_CENTER, gridToCRS, crs);
-        final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_BYTE_BINARY);
-        resource = new MemoryGridResource(null, null, new GridCoverage2D(grid, null, image), null);
+        final var grid = new GridGeometry(new GridExtent(WIDTH, HEIGHT), PixelInCell.CELL_CENTER, gridToCRS, crs);
+        final var image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_BYTE_BINARY);
+        resource = new MemoryGridCoverageResource(null, new GridCoverage2D(grid, null, image), null);
     }
 
     /**
      * Creates an arbitrary grid geometry included inside the {@linkplain #resource} extent.
      */
     private GridGeometry createSubGrid() {
-        final GridExtent extent = new GridExtent(null,
+        final var extent = new GridExtent(null,
                 new long[] {7, 4},
                 new long[] {WIDTH - 9, HEIGHT - 11}, true);
 
@@ -82,7 +83,7 @@ public final class MemoryGridResourceTest extends TestCase {
     }
 
     /**
-     * Tests {@link MemoryGridResource#read(GridGeometry, int...)}.
+     * Tests {@link MemoryGridCoverageResource#read(GridGeometry, int[])}.
      */
     @Test
     public void testRead() {
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
index adc7968511..4c02d10c85 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
@@ -33,7 +33,7 @@ import org.apache.sis.coverage.grid.BufferedGridCoverage;
 import org.apache.sis.coverage.grid.GridCoverageProcessor;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
-import org.apache.sis.storage.base.MemoryGridResource;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
@@ -48,6 +48,7 @@ import org.apache.sis.referencing.crs.HardCodedCRS;
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class BandAggregateGridResourceTest extends TestCase {
     /**
      * Width and height of images created for tests.
@@ -65,7 +66,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
     private final GridCoverageProcessor processor;
 
     /**
-     * Whether to hide {@link MemoryGridResource} in order to disable optimizations.
+     * Whether to hide {@link MemoryGridCoverageResource} in order to disable optimizations.
      */
     private boolean opaque;
 
@@ -153,7 +154,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
         final var aggregator = new CoverageAggregator(null, processor);
         aggregator.addRangeAggregate(first, second);
         /*
-         * If the result is not an instance of `MemoryGridResource`,
+         * If the result is not an instance of `MemoryGridCoverageResource`,
          * this is a bug in `BandAggregateGridResource.create(…)`.
          */
         final LocalName testName = Names.createLocalName(null, null, "test-name");
@@ -167,7 +168,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
     }
 
     /**
-     * Tests the shortcut for {@link MemoryGridResource} instances.
+     * Tests the shortcut for {@link MemoryGridCoverageResource} instances.
      * {@link BandAggregateGridResource} should apply aggregations directly on the underlying grid coverages.
      *
      * @throws DataStoreException if an error occurred while reading a resource.
@@ -200,7 +201,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
             System.arraycopy(bandValues, 0, data, i, numBands);
         }
         final var values = new DataBufferInt(data, data.length);
-        final var r = new MemoryGridResource(null, null, new BufferedGridCoverage(domain, samples, values), null);
+        final var r = new MemoryGridCoverageResource(null, new BufferedGridCoverage(domain, samples, values), null);
         return opaque ? new OpaqueGridResource(r) : r;
     }
 
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java
index cd509bf382..f0e4fe4883 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/ConcatenatedFeatureSetTest.java
@@ -23,7 +23,7 @@ import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.base.MemoryFeatureSet;
+import org.apache.sis.storage.MemoryFeatureSet;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/CoverageMosaicTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/CoverageMosaicTest.java
index dccd97d35c..cf337d3f82 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/CoverageMosaicTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/CoverageMosaicTest.java
@@ -52,6 +52,7 @@ import org.apache.sis.test.TestUtilities;
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public final class CoverageMosaicTest extends TestCase {
     /**
@@ -367,10 +368,10 @@ public final class CoverageMosaicTest extends TestCase {
         assertEquals(ranges, coverage.getSampleDimensions());
         GridExtent extent = coverage.getGridGeometry().getExtent();
         /*
-         * At the time of writing this test, the `MemoryGridResource.read(…)` implementation
+         * At the time of writing this test, the `MemoryGridCoverageResource.read(…)` implementation
          * does not apply subsampling. Therefore, the coverage is as `testReadTilesFully()`.
          * If a test failure happens below in a future version, this is not necessarily a bug.
-         * Maybe `MemoryGridResource` has been improved and this test should be adjusted.
+         * Maybe `MemoryGridCoverageResource` has been improved and this test should be adjusted.
          */
         assertEquals(NUM_TILES*TILE_WIDTH,  extent.getSize(0));
         assertEquals(NUM_TILES*TILE_HEIGHT, extent.getSize(1));
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
index 2034ca8e58..a284783097 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
@@ -27,7 +27,7 @@ import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.internal.shared.AttributeConvention;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.base.MemoryFeatureSet;
+import org.apache.sis.storage.MemoryFeatureSet;
 
 // Test dependencies
 import org.junit.jupiter.api.Test;
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/OpaqueGridResource.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/OpaqueGridResource.java
index 671b6b3e83..4bb8c7272e 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/OpaqueGridResource.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/OpaqueGridResource.java
@@ -18,13 +18,13 @@ package org.apache.sis.storage.aggregate;
 
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 import org.apache.sis.storage.base.GridResourceWrapper;
-import org.apache.sis.storage.base.MemoryGridResource;
 
 
 /**
  * Wraps a grid coverage resource without changing anything. This is used for disabling optimizations,
- * in order to test different code paths that what would be used with {@code MemoryGridResource}.
+ * in order to test different code paths that what would be used with {@code MemoryGridCoverageResource}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
@@ -38,7 +38,7 @@ final class OpaqueGridResource extends GridResourceWrapper {
      * Creates a new wrapper for the given coverage.
      */
     OpaqueGridResource(final GridCoverage source) {
-        this.source = new MemoryGridResource(null, null, source, null);
+        this.source = new MemoryGridCoverageResource(null, source, null);
     }
 
     /**
diff --git a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
index aeb8903057..06797449e3 100644
--- a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
+++ b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
@@ -20,7 +20,6 @@ import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -29,7 +28,6 @@ import org.locationtech.jts.geom.CoordinateXY;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
-import org.opengis.util.GenericName;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.Metadata;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -52,7 +50,7 @@ import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.base.MemoryFeatureSet;
+import org.apache.sis.storage.MemoryFeatureSet;
 import org.apache.sis.style.se1.FeatureTypeStyle;
 import org.apache.sis.style.se1.Symbology;
 import org.apache.sis.style.se1.StyleFactory;
@@ -60,8 +58,6 @@ import org.apache.sis.style.se1.Symbolizer;
 import org.apache.sis.style.se1.SemanticType;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.storage.event.StoreEvent;
-import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.util.iso.Names;
 
 // Test dependencies
@@ -543,21 +539,10 @@ public class SEPortrayerTest {
                 return list;
             }
 
-            @Override
-            public Optional<GenericName> getIdentifier() throws DataStoreException {
-                return Optional.empty();
-            }
-
             @Override
             public Metadata getMetadata() throws DataStoreException {
                 return null;
             }
-
-            @Override
-            public <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) {}
-
-            @Override
-            public <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) {}
         };
 
         final MapLayer aggLayer = new MapLayer();
diff --git a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java
index fa435f29b3..711db8eb5b 100644
--- a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java
+++ b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java
@@ -39,7 +39,7 @@ import org.apache.sis.map.SEPresentation;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.base.MemoryFeatureSet;
+import org.apache.sis.storage.MemoryFeatureSet;
 import org.apache.sis.style.se1.FeatureTypeStyle;
 import org.apache.sis.style.se1.LineSymbolizer;
 import org.apache.sis.style.se1.Rule;
diff --git a/incubator/src/org.apache.sis.storage.coveragejson/test/org/apache/sis/storage/coveragejson/CoverageJsonStoreTest.java b/incubator/src/org.apache.sis.storage.coveragejson/test/org/apache/sis/storage/coveragejson/CoverageJsonStoreTest.java
index eda64970b1..0a9d1d9027 100644
--- a/incubator/src/org.apache.sis.storage.coveragejson/test/org/apache/sis/storage/coveragejson/CoverageJsonStoreTest.java
+++ b/incubator/src/org.apache.sis.storage.coveragejson/test/org/apache/sis/storage/coveragejson/CoverageJsonStoreTest.java
@@ -34,10 +34,10 @@ import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.MemoryGridCoverageResource;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.WritableAggregate;
-import org.apache.sis.storage.base.MemoryGridResource;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 
@@ -132,7 +132,7 @@ public class CoverageJsonStoreTest {
             gcb.setValues(image);
             final GridCoverage coverage = gcb.build();
 
-            final GridCoverageResource gcr = new MemoryGridResource(null, null, coverage, null);
+            final GridCoverageResource gcr = new MemoryGridCoverageResource(null, coverage, null);
 
             aggregate.add(gcr);
 
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/RootResource.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/RootResource.java
index 8bcc1722ef..43466ea1de 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/RootResource.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/RootResource.java
@@ -19,14 +19,10 @@ package org.apache.sis.gui.dataset;
 import java.util.AbstractList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Optional;
 import javafx.scene.control.TreeItem;
 import org.opengis.metadata.Metadata;
-import org.opengis.util.GenericName;
 import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.event.StoreEvent;
-import org.apache.sis.storage.event.StoreListener;
 
 
 /**
@@ -109,14 +105,6 @@ final class RootResource implements Aggregate {
         };
     }
 
-    /**
-     * Returns empty optional since this resource has no identifier.
-     */
-    @Override
-    public Optional<GenericName> getIdentifier() {
-        return Optional.empty();
-    }
-
     /**
      * Returns null since this resource has no metadata. Returning null is normally
      * not allowed for this method, but {@link ResourceTree} is robust to this case.
@@ -125,8 +113,4 @@ final class RootResource implements Aggregate {
     public Metadata getMetadata() {
         return null;
     }
-
-    /** Ignored since this class does not emit any event. */
-    @Override public <T extends StoreEvent> void    addListener(Class<T> eventType, StoreListener<? super T> listener) {}
-    @Override public <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) {}
 }