sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Modify the policy about default implementation in AbstractResource: construct metadata from getIdentifier() and getEnvelope() instead than providing a default implementation of those methods based on metadata. A side effect of this work is to provide an implementation of JoinFeatureSet.getEnvelope() based on newly added Envelopes.union(Envelope...) ùethod.
Date Sun, 18 Nov 2018 23:30:00 GMT
This is an automated email from the ASF dual-hosted git repository.

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 8e76ba4  Modify the policy about default implementation in AbstractResource: construct metadata from getIdentifier() and getEnvelope() instead than providing a default implementation of those methods based on metadata. A side effect of this work is to provide an implementation of JoinFeatureSet.getEnvelope() based on newly added Envelopes.union(Envelope...) ùethod.
8e76ba4 is described below

commit 8e76ba427e64e892f7be45580a77f4fa8d5362df
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Nov 19 00:22:12 2018 +0100

    Modify the policy about default implementation in AbstractResource: construct metadata from getIdentifier() and getEnvelope() instead than providing a default implementation of those methods based on metadata. A side effect of this work is to provide an implementation of JoinFeatureSet.getEnvelope() based on newly added Envelopes.union(Envelope...) ùethod.
---
 .../org/apache/sis/geometry/EnvelopeReducer.java   | 160 +++++++++++++++++++++
 .../java/org/apache/sis/geometry/Envelopes.java    |  40 ++++++
 .../org/apache/sis/geometry/GeneralEnvelope.java   |   2 +
 .../apache/sis/internal/referencing/Resources.java |   5 +
 .../sis/internal/referencing/Resources.properties  |   1 +
 .../internal/referencing/Resources_fr.properties   |   1 +
 .../main/java/org/apache/sis/referencing/CRS.java  |   3 +-
 .../apache/sis/geometry/EnvelopeReducerTest.java   |  95 ++++++++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 .../sis/storage/geotiff/ImageFileDirectory.java    |   5 +-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |   9 --
 .../sis/internal/netcdf/ucar/FeaturesWrapper.java  |   6 -
 .../sis/internal/storage/AbstractFeatureSet.java   |  43 ++----
 .../sis/internal/storage/AbstractGridResource.java |  71 +++++++++
 .../sis/internal/storage/AbstractResource.java     | 120 ++++------------
 .../sis/internal/storage/JoinFeatureSet.java       |  52 ++++---
 .../sis/internal/storage/MemoryFeatureSet.java     |  11 --
 .../sis/internal/storage/StoreUtilities.java       |  37 ++++-
 .../sis/internal/storage/query/FeatureSubset.java  |   9 --
 .../main/java/org/apache/sis/storage/DataSet.java  |   5 +-
 .../java/org/apache/sis/storage/DataStore.java     |  31 +++-
 .../org/apache/sis/internal/storage/gpx/Store.java |   4 +-
 22 files changed, 527 insertions(+), 184 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/EnvelopeReducer.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/EnvelopeReducer.java
new file mode 100644
index 0000000..e4b4752
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/EnvelopeReducer.java
@@ -0,0 +1,160 @@
+/*
+ * 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.geometry;
+
+import org.apache.sis.internal.referencing.Resources;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.Utilities;
+
+
+/**
+ * Applies union or intersection operations on a sequence of envelopes.
+ * This utility class infers the a common coordinate reference system
+ * for performing the reduce operation.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ *
+ * @see Envelopes#union(Envelope...)
+ * @see Envelopes#intersect(Envelope...)
+ *
+ * @since 1.0
+ * @module
+ */
+class EnvelopeReducer {
+    /**
+     * A reducer performing the {@linkplain GeneralEnvelope#add(Envelope) union} operation.
+     *
+     * @see Envelopes#union(Envelope...)
+     */
+    static final EnvelopeReducer UNION = new EnvelopeReducer();
+
+    /**
+     * A reducer performing the {@linkplain GeneralEnvelope#intersect(Envelope) intersection} operation.
+     *
+     * @see Envelopes#intersect(Envelope...)
+     */
+    static final EnvelopeReducer INTERSECT = new EnvelopeReducer() {
+        @Override void reduce(GeneralEnvelope result, Envelope other) {
+            result.intersect(other);
+        }
+
+        @Override void reduce(DefaultGeographicBoundingBox result, GeographicBoundingBox other) {
+            result.intersect(other);
+        }
+    };
+
+    /**
+     * Creates a new reducer. We should have a singleton instance for each type of reduce operation.
+     */
+    EnvelopeReducer() {
+    }
+
+    /**
+     * Applies the reduce operation on the given {@code result} envelope.
+     * The result is modified in-place.
+     */
+    void reduce(GeneralEnvelope result, Envelope other) {
+        result.add(other);
+    }
+
+    /**
+     * Applies the reduce operation on the given {@code result} bounding box.
+     * The result is modified in-place.
+     */
+    void reduce(DefaultGeographicBoundingBox result, GeographicBoundingBox other) {
+        result.add(other);
+    }
+
+    /**
+     * Reduces all given envelopes, transforming them to a common CRS if necessary.
+     * If all envelopes use the same CRS (ignoring metadata) or if the CRS of all envelopes is {@code null},
+     * then the reduce operation is performed without transforming any envelope. Otherwise all envelopes are
+     * transformed to a {@linkplain CRS#suggestCommonTarget common CRS} before reduction.
+     * The CRS of the returned envelope may different than the CRS of all given envelopes.
+     *
+     * @param  envelopes  the envelopes for which to perform the reduce operation. Null elements are ignored.
+     * @return result of reduce operation, or {@code null} if the given array does not contain non-null elements.
+     * @throws TransformException if this method can not determine a common CRS, or if a transformation failed.
+     */
+    final GeneralEnvelope reduce(final Envelope[] envelopes) throws TransformException {
+        /*
+         * First, compute the unions or intersections of all envelopes having a common CRS
+         * without performing any reprojection. In the common case where all envelopes use
+         * the same CRS, this will result in an array having only one non-null element.
+         */
+        final GeneralEnvelope[] reduced = new GeneralEnvelope[envelopes.length];
+        int count = 0;
+merge:  for (final Envelope envelope : envelopes) {
+            if (envelope != null) {
+                final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
+                for (int i=0; i<count; i++) {
+                    final GeneralEnvelope previous = reduced[i];
+                    if (Utilities.equalsIgnoreMetadata(crs, previous.getCoordinateReferenceSystem())) {
+                        reduce(previous, envelope);
+                        continue merge;
+                    }
+                }
+                reduced[count++] = new GeneralEnvelope(envelope);
+            }
+        }
+        switch (count) {
+            case 0: return null;
+            case 1: return reduced[0];
+        }
+        /*
+         * Compute the geographic bounding box of all remaining elements to reduce.
+         * This will be used for choosing a common CRS.
+         */
+        CoordinateReferenceSystem[]  crs  = new CoordinateReferenceSystem[count];
+        DefaultGeographicBoundingBox more = new DefaultGeographicBoundingBox();
+        DefaultGeographicBoundingBox bbox = null;
+        for (int i=0; i<count; i++) {
+            final GeneralEnvelope e = reduced[i];
+            crs[i] = e.getCoordinateReferenceSystem();
+            more.setBounds(e);
+            if (i == 0) {
+                bbox = more;
+                more = new DefaultGeographicBoundingBox();
+            } else {
+                reduce(bbox, more);
+            }
+        }
+        /*
+         * Now transform all remaining envelopes, so we can perform final reduction.
+         */
+        final CoordinateReferenceSystem target = CRS.suggestCommonTarget(bbox, crs);
+        if (target == null) {
+            throw new TransformException(Resources.format(Resources.Keys.CanNotFindCommonCRS));
+        }
+        GeneralEnvelope result = null;
+        for (int i=0; i<count; i++) {
+            final Envelope other = Envelopes.transform(reduced[i], target);
+            if (result == null) {
+                result = GeneralEnvelope.castOrCopy(other);
+            } else {
+                reduce(result, other);
+            }
+        }
+        return result;
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
index 0bea159..243d58a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
@@ -156,6 +156,46 @@ public final class Envelopes extends Static {
     }
 
     /**
+     * Computes the union of all given envelopes, transforming them to a common CRS if necessary.
+     * If all envelopes use the same CRS ({@link ComparisonMode#IGNORE_METADATA ignoring metadata})
+     * or if the CRS of all envelopes is {@code null}, then the {@linkplain GeneralEnvelope#add(Envelope)
+     * union is computed} without transforming any envelope. Otherwise all envelopes are transformed to a
+     * {@linkplain CRS#suggestCommonTarget common CRS} before union is computed.
+     * The CRS of the returned envelope may different than the CRS of all given envelopes.
+     *
+     * @param  envelopes  the envelopes for which to compute union. Null elements are ignored.
+     * @return union of given envelopes, or {@code null} if the given array does not contain non-null elements.
+     * @throws TransformException if this method can not determine a common CRS, or if a transformation failed.
+     *
+     * @see GeneralEnvelope#add(Envelope)
+     *
+     * @since 1.0
+     */
+    public static GeneralEnvelope union(final Envelope... envelopes) throws TransformException {
+        return EnvelopeReducer.UNION.reduce(envelopes);
+    }
+
+    /**
+     * Computes the intersection of all given envelopes, transforming them to a common CRS if necessary.
+     * If all envelopes use the same CRS ({@link ComparisonMode#IGNORE_METADATA ignoring metadata})
+     * or if the CRS of all envelopes is {@code null}, then the {@linkplain GeneralEnvelope#intersect(Envelope)
+     * intersection is computed} without transforming any envelope. Otherwise all envelopes are transformed to a
+     * {@linkplain CRS#suggestCommonTarget common CRS} before intersection is computed.
+     * The CRS of the returned envelope may different than the CRS of all given envelopes.
+     *
+     * @param  envelopes  the envelopes for which to compute intersection. Null elements are ignored.
+     * @return intersection of given envelopes, or {@code null} if the given array does not contain non-null elements.
+     * @throws TransformException if this method can not determine a common CRS, or if a transformation failed.
+     *
+     * @see GeneralEnvelope#intersect(Envelope)
+     *
+     * @since 1.0
+     */
+    public static GeneralEnvelope intersect(final Envelope... envelopes) throws TransformException {
+        return EnvelopeReducer.INTERSECT.reduce(envelopes);
+    }
+
+    /**
      * Invoked when a recoverable exception occurred. Those exceptions must be minor enough
      * that they can be silently ignored in most cases.
      */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
index cf2f0fb..195fc27 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
@@ -601,6 +601,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable, Seriali
      * @throws MismatchedDimensionException if the given envelope does not have the expected number of dimensions.
      * @throws AssertionError if assertions are enabled and the envelopes have mismatched CRS.
      *
+     * @see Envelopes#union(Envelope...)
      * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#add(GeographicBoundingBox)
      */
     public void add(final Envelope envelope) throws MismatchedDimensionException {
@@ -726,6 +727,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable, Seriali
      * @throws MismatchedDimensionException if the given envelope does not have the expected number of dimensions.
      * @throws AssertionError if assertions are enabled and the envelopes have mismatched CRS.
      *
+     * @see Envelopes#intersect(Envelope...)
      * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#intersect(GeographicBoundingBox)
      */
     public void intersect(final Envelope envelope) throws MismatchedDimensionException {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
index 3f22dcb..4688fda 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java
@@ -88,6 +88,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotCreateObjectAsInstanceOf_2 = 4;
 
         /**
+         * Can not find a coordinate reference system common to all given envelopes.
+         */
+        public static final short CanNotFindCommonCRS = 82;
+
+        /**
          * Can not infer a grid size from the given values in {0} range.
          */
         public static final short CanNotInferGridSizeFromValues_1 = 75;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
index 0fb2c64..c11c140 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties
@@ -46,6 +46,7 @@ CanNotCombineUriAsType_1          = Can not create objects of type \u2018{0}\u20
 CanNotComputeDerivative           = Can not compute the coordinate operation derivative.
 CanNotConcatenateTransforms_2     = Can not concatenate transforms \u201c{0}\u201d and \u201c{1}\u201d.
 CanNotCreateObjectAsInstanceOf_2  = Can not create an object of type \u201c{1}\u201d as an instance of class \u2018{0}\u2019.
+CanNotFindCommonCRS               = Can not find a coordinate reference system common to all given envelopes.
 CanNotInferGridSizeFromValues_1   = Can not infer a grid size from the given values in {0} range.
 CanNotInstantiateGeodeticObject_1 = Can not instantiate geodetic object for \u201c{0}\u201d.
 CanNotMapAxisToDirection_1        = Can not map an axis from the specified coordinate system to the \u201c{0}\u201d direction.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
index 41cf4df..6f829a7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties
@@ -51,6 +51,7 @@ CanNotCombineUriAsType_1          = Ne peut pas cr\u00e9er d\u2019objets de type
 CanNotComputeDerivative           = La d\u00e9riv\u00e9 de l\u2019op\u00e9ration sur les coordonn\u00e9es ne peut pas \u00eatre calcul\u00e9e.
 CanNotConcatenateTransforms_2     = Les transformations \u00ab\u202f{0}\u202f\u00bb et \u00ab\u202f{1}\u202f\u00bb ne peuvent pas \u00eatre combin\u00e9es.
 CanNotCreateObjectAsInstanceOf_2  = Ne peut pas cr\u00e9er un objet du type \u00ab\u202f{1}\u202f\u00bb comme une instance de la classe \u2018{0}\u2019.
+CanNotFindCommonCRS               = Ne peut pas trouver un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es commun pour toutes les enveloppes donn\u00e9es.
 CanNotInferGridSizeFromValues_1   = Ne peut pas inf\u00e9rer une taille de grille \u00e0 partir des valeurs donn\u00e9es dans la plage {0}.
 CanNotInstantiateGeodeticObject_1 = Ne peut pas cr\u00e9er l\u2019objet g\u00e9od\u00e9tique pour \u00ab\u202f{0}\u202f\u00bb.
 CanNotMapAxisToDirection_1        = Aucun axe du syst\u00e8me de coordonn\u00e9es sp\u00e9cifi\u00e9 n\u2019a pu \u00eatre associ\u00e9 \u00e0 la direction \u00ab\u202f{0}\u202f\u00bb.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
index 30dd2f1..abce0c2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
@@ -442,8 +442,9 @@ public final class CRS extends Static {
      * for choosing a common CRS which is less likely to fail.</div>
      *
      * @param  regionOfInterest  the geographic area for which the coordinate operations will be applied,
-     *                           or {@code null} if unknown.
+     *                           or {@code null} if unknown. Will be intersected with CRS domains of validity.
      * @param  sourceCRS         the coordinate reference systems for which a common target CRS is desired.
+     *                           May contain {@code null} elements, in which case this method returns {@code null}.
      * @return a CRS that may be used as a common target for all the given source CRS in the given region of interest,
      *         or {@code null} if this method did not find a common target CRS. The returned CRS may be different than
      *         all given CRS.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopeReducerTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopeReducerTest.java
new file mode 100644
index 0000000..f32fd09
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopeReducerTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.geometry;
+
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.ReferencingAssert.*;
+
+
+/**
+ * Tests the {@link EnvelopeReducer} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+@DependsOn(EnvelopesTest.class)
+public final strictfp class EnvelopeReducerTest extends TestCase {
+    /**
+     * The envelopes on which to perform an operation.
+     */
+    private final GeneralEnvelope[] sources;
+
+    /**
+     * Creates an envelope for the given coordinate values.
+     */
+    private static GeneralEnvelope createFromExtremums(boolean latlon, double xmin, double ymin, double xmax, double ymax) {
+        final CommonCRS crs = CommonCRS.WGS84;
+        final GeneralEnvelope env;
+        if (latlon) {
+            env = new GeneralEnvelope(crs.geographic());
+            env.setRange(1, xmin, xmax);
+            env.setRange(0, ymin, ymax);
+        } else {
+            env = new GeneralEnvelope(crs.normalizedGeographic());
+            env.setRange(0, xmin, xmax);
+            env.setRange(1, ymin, ymax);
+        }
+        return env;
+    }
+
+    /**
+     * Creates a new test case.
+     */
+    public EnvelopeReducerTest() {
+        sources = new GeneralEnvelope[] {
+            createFromExtremums(true,  50, -5, 60, 15),
+            createFromExtremums(false, 44,  3, 52, 16),
+            createFromExtremums(true,  48, -2, 54, 12)
+        };
+    }
+
+    /**
+     * Test {@link Envelopes#union(Envelope...)} method.
+     *
+     * @throws TransformException if an error occurred while transforming an envelope.
+     */
+    @Test
+    public void testUnion() throws TransformException {
+        final GeneralEnvelope expected = createFromExtremums(true, 44, -5, 60, 16);
+        final GeneralEnvelope actual = Envelopes.union(sources);
+        assertEnvelopeEquals(expected, actual, STRICT);
+    }
+
+    /**
+     * Test {@link Envelopes#intersect(Envelope...)} method.
+     *
+     * @throws TransformException if an error occurred while transforming an envelope.
+     */
+    @Test
+    public void testIntersect() throws TransformException {
+        final GeneralEnvelope expected = createFromExtremums(true, 50, 3, 52, 12);
+        final GeneralEnvelope actual = Envelopes.intersect(sources);
+        assertEnvelopeEquals(expected, actual, STRICT);
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 3a6f889..d1465d5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -250,6 +250,7 @@ import org.junit.BeforeClass;
     org.apache.sis.geometry.CurveExtremumTest.class,
     org.apache.sis.geometry.Shapes2DTest.class,                 // Simpler than EnvelopesTest.
     org.apache.sis.geometry.EnvelopesTest.class,
+    org.apache.sis.geometry.EnvelopeReducerTest.class,
     org.apache.sis.internal.referencing.ServicesForMetadataTest.class,
     org.apache.sis.internal.metadata.EllipsoidalHeightCombinerTest.class,
     org.apache.sis.geometry.CoordinateFormatTest.class,
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 16e57b2..4cc3df0 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -30,12 +30,11 @@ import org.opengis.metadata.citation.DateType;
 import org.opengis.util.FactoryException;
 import org.opengis.util.GenericName;
 import org.apache.sis.internal.geotiff.Resources;
-import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.internal.storage.AbstractGridResource;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
-import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.math.Vector;
@@ -57,7 +56,7 @@ import org.apache.sis.measure.Units;
  * @since 0.8
  * @module
  */
-final class ImageFileDirectory extends AbstractResource implements GridCoverageResource {
+final class ImageFileDirectory extends AbstractGridResource {
     /**
      * Possible value for the {@link #tileTagFamily} field. That field tells whether image tiling
      * was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*} family.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
index 764afe6..dbc84e1 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
@@ -28,7 +28,6 @@ import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import java.util.function.Consumer;
 import java.io.IOException;
-import org.opengis.util.GenericName;
 import org.apache.sis.math.Vector;
 import org.apache.sis.internal.netcdf.DataType;
 import org.apache.sis.internal.netcdf.DiscreteSampling;
@@ -153,14 +152,6 @@ final class FeaturesInfo extends DiscreteSampling {
     }
 
     /**
-     * Returns an identifier for the collection of features in the netCDF file.
-     */
-    @Override
-    public GenericName getIdentifier() {
-        return type.getName();
-    }
-
-    /**
      * Returns {@code true} if the given attribute value is one of the {@code cf_role} attribute values
      * supported by this implementation.
      */
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
index 226c977..4072b5c 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.netcdf.ucar;
 
 import java.util.stream.Stream;
-import org.opengis.util.GenericName;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.internal.netcdf.DiscreteSampling;
@@ -56,11 +55,6 @@ final class FeaturesWrapper extends DiscreteSampling {
     }
 
     @Override
-    public GenericName getIdentifier() {
-        throw new UnsupportedOperationException();      // TODO
-    }
-
-    @Override
     public FeatureType getType() {
         throw new UnsupportedOperationException();      // TODO
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
index 8cd92d9..e386c4e 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
@@ -22,11 +22,7 @@ import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.Query;
 import org.apache.sis.storage.UnsupportedQueryException;
 import org.apache.sis.internal.storage.query.SimpleQuery;
-import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.metadata.iso.citation.DefaultCitation;
-import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
 import org.apache.sis.util.logging.WarningListeners;
-import org.opengis.metadata.Metadata;
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
@@ -44,13 +40,6 @@ import org.opengis.feature.FeatureType;
  */
 public abstract class AbstractFeatureSet extends AbstractResource implements FeatureSet {
     /**
-     * A description of this set of features, or {@code null} if not yet computed.
-     * Those metadata are created by {@link #getMetadata()} when first needed.
-     * Subclasses can set a value to this field directly.
-     */
-    protected Metadata metadata;
-
-    /**
      * Creates a new resource.
      *
      * @param listeners  the set of registered warning listeners for the data store, or {@code null} if none.
@@ -70,32 +59,20 @@ public abstract class AbstractFeatureSet extends AbstractResource implements Fea
     }
 
     /**
-     * Returns a description of this set of features.
-     * Current implementation sets only the resource name; this may change in any future Apache SIS version.
+     * Returns the feature type name as the identifier for this resource.
+     *
+     * @return the resource identifier inferred from metadata, or {@code null} if none or ambiguous.
+     * @throws DataStoreException if an error occurred while fetching the identifier.
      *
-     * <div class="note"><b>Note:</b>
-     * we currently do not set the geographic extent from the envelope because default {@link #getEnvelope()}
-     * implementation itself invokes {@code getMetadata()}. Consequently requesting the envelope from this
-     * method could create a never-ending loop.</div>
+     * @see DataStore#getIdentifier()
      */
     @Override
-    public synchronized Metadata getMetadata() throws DataStoreException {
-        if (metadata == null) {
-            final DefaultMetadata metadata = new DefaultMetadata();
-            final FeatureType type = getType();
-            if (type != null) {
-                final GenericName name = type.getName();
-                if (name != null) {                         // Paranoiac check (should never be null).
-                    final DefaultCitation citation = new DefaultCitation(name.toInternationalString());
-                    final DefaultDataIdentification identification = new DefaultDataIdentification();
-                    identification.setCitation(citation);
-                }
-            }
-            // No geographic extent - see above javadoc.
-            metadata.transition(DefaultMetadata.State.FINAL);
-            this.metadata = metadata;
+    public GenericName getIdentifier() throws DataStoreException {
+        final FeatureType type = getType();
+        if (type != null) {
+            return type.getName();
         }
-        return metadata;
+        return null;
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
new file mode 100644
index 0000000..9d967bb
--- /dev/null
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage;
+
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.Resource;
+import org.apache.sis.util.logging.WarningListeners;
+import org.opengis.geometry.Envelope;
+
+
+/**
+ * Base class for implementations of {@link GridCoverageResource}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   0.8
+ * @module
+ */
+public abstract class AbstractGridResource extends AbstractResource implements GridCoverageResource {
+    /**
+     * Creates a new resource.
+     *
+     * @param listeners  the set of registered warning listeners for the data store, or {@code null} if none.
+     */
+    protected AbstractGridResource(final WarningListeners<DataStore> listeners) {
+        super(listeners);
+    }
+
+    /**
+     * Creates a new resource with the same warning listeners than the given resource,
+     * or {@code null} if the listeners are unknown.
+     *
+     * @param resource  the resources from which to get the listeners, or {@code null} if none.
+     */
+    protected AbstractGridResource(final Resource resource) {
+        super(resource);
+    }
+
+    /**
+     * Returns the grid geometry envelope, or {@code null} if unknown.
+     * This implementation fetches the envelope from the grid geometry instead than from metadata.
+     *
+     * @return the grid geometry envelope, or {@code null}.
+     * @throws DataStoreException if an error occurred while computing the grid geometry.
+     */
+    @Override
+    public Envelope getEnvelope() throws DataStoreException {
+        final GridGeometry gg = getGridGeometry();
+        if (gg != null && gg.isDefined(GridGeometry.ENVELOPE)) {
+            return gg.getEnvelope();
+        }
+        return null;
+    }
+}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
index 511d86a..69f973f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractResource.java
@@ -18,16 +18,8 @@ package org.apache.sis.internal.storage;
 
 import java.util.Locale;
 import org.opengis.util.GenericName;
-import org.opengis.geometry.Envelope;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.metadata.extent.GeographicExtent;
-import org.opengis.metadata.identification.Identification;
-import org.apache.sis.referencing.NamedIdentifier;
-import org.apache.sis.geometry.GeneralEnvelope;
+import org.opengis.geometry.Envelope;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.storage.Resource;
@@ -35,6 +27,9 @@ import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.ChangeEvent;
 import org.apache.sis.storage.event.ChangeListener;
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
 
 
 /**
@@ -50,6 +45,16 @@ import org.apache.sis.storage.event.ChangeListener;
  */
 public abstract class AbstractResource implements Resource, Localized {
     /**
+     * A description of this resource as an unmodifiable metadata, or {@code null} if not yet computed.
+     * If non-null, this metadata shall contain at least the resource {@linkplain #getIdentifier() identifier}.
+     *
+     * <p>Those metadata are created by {@link #getMetadata()} when first needed.
+     * Subclasses can set a value to this field directly if they wish to bypass the
+     * default metadata creation process.</p>
+     */
+    protected Metadata metadata;
+
+    /**
      * The set of registered warning listeners for the data store, or {@code null} if none.
      */
     protected final WarningListeners<DataStore> listeners;
@@ -97,98 +102,35 @@ public abstract class AbstractResource implements Resource, Localized {
     }
 
     /**
-     * Returns an identifier for this resource. The default implementation returns the first identifier
-     * of {@code Metadata/​identificationInfo/​citation}, provided that exactly one such citation is found.
-     * If more than one citation is found, then this method returns {@code null} since the identification
-     * is considered ambiguous. This is the same default implementation than {@link DataStore#getIdentifier()}.
-     *
-     * @return the resource identifier inferred from metadata, or {@code null} if none or ambiguous.
-     * @throws DataStoreException if an error occurred while fetching the identifier.
-     *
-     * @see DataStore#getIdentifier()
-     */
-    @Override
-    public GenericName getIdentifier() throws DataStoreException {
-        return identifier(getMetadata());
-    }
-
-    /**
-     * Implementation of {@link #getIdentifier()}, provided as a separated method for implementations
-     * that do not extend {@code AbstractResource}.
-     *
-     * @param  metadata  the metadata from which to infer the identifier, or {@code null}.
-     * @return the resource identifier inferred from metadata, or {@code null} if none or ambiguous.
-     *
-     * @see StoreUtilities#getAnyIdentifier(Metadata, boolean)
-     */
-    public static GenericName identifier(final Metadata metadata) {
-        if (metadata != null) {
-            Citation citation = null;
-            for (final Identification id : metadata.getIdentificationInfo()) {
-                final Citation c = id.getCitation();
-                if (c != null) {
-                    if (citation != null && citation != c) return null;                 // Ambiguity.
-                    citation = c;
-                }
-            }
-            if (citation != null) {
-                Identifier first = null;
-                for (final Identifier c : citation.getIdentifiers()) {
-                    if (c instanceof GenericName) {
-                        return (GenericName) c;
-                    } else if (first == null) {
-                        first = c;
-                    }
-                }
-                if (first != null) {
-                    return new NamedIdentifier(first);
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns the spatio-temporal envelope of this resource.
-     * The default implementation computes the union of all {@link GeographicBoundingBox} in the resource metadata,
-     * assuming the {@linkplain org.apache.sis.referencing.CommonCRS#defaultGeographic() default geographic CRS}
-     * (usually WGS 84).
+     * The default implementation returns {@code null}.
      *
      * @return the spatio-temporal resource extent, or {@code null} if none.
-     * @throws DataStoreException if an error occurred while reading or computing the envelope.
      *
-     * @see org.apache.sis.storage.DataSet#getEnvelope()
+     * @throws DataStoreException if an error occurred while reading or computing the envelope.
      */
     public Envelope getEnvelope() throws DataStoreException {
-        return envelope(getMetadata());
+        return null;
     }
 
     /**
-     * Implementation of {@link #getEnvelope()}, provided as a separated method for
-     * {@link org.apache.sis.storage.DataSet} implementations that do not extend {@code AbstractResource}.
-     *
-     * @param  metadata  the metadata from which to compute the envelope, or {@code null}.
-     * @return the spatio-temporal resource extent, or {@code null} if none.
+     * Returns a description of this set of features.
+     * Current implementation sets only the resource name; this may change in any future Apache SIS version.
      */
-    public static Envelope envelope(final Metadata metadata) {
-        GeneralEnvelope bounds = null;
-        if (metadata != null) {
-            for (final Identification identification : metadata.getIdentificationInfo()) {
-                for (final Extent extent : identification.getExtents()) {
-                    for (final GeographicExtent ge : extent.getGeographicElements()) {
-                        if (ge instanceof GeographicBoundingBox) {
-                            final GeneralEnvelope env = new GeneralEnvelope((GeographicBoundingBox) ge);
-                            if (bounds == null) {
-                                bounds = env;
-                            } else {
-                                bounds.add(env);
-                            }
-                        }
-                    }
-                }
+    @Override
+    public synchronized Metadata getMetadata() throws DataStoreException {
+        if (metadata == null) {
+            final DefaultMetadata metadata = new DefaultMetadata();
+            final GenericName name = getIdentifier();
+            if (name != null) {                         // Paranoiac check (should never be null).
+                final DefaultCitation citation = new DefaultCitation(name.toInternationalString());
+                final DefaultDataIdentification identification = new DefaultDataIdentification();
+                identification.setCitation(citation);
             }
+            metadata.transition(DefaultMetadata.State.FINAL);
+            this.metadata = metadata;
         }
-        return bounds;
+        return metadata;
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
index f7e6e7f..27e22a4 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
@@ -17,6 +17,8 @@
 package org.apache.sis.internal.storage;
 
 import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Spliterator;
 import java.util.function.Consumer;
@@ -24,14 +26,17 @@ import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import org.opengis.util.GenericName;
 import org.opengis.geometry.Envelope;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.feature.FeatureOperations;
 import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.feature.DefaultAssociationRole;
 import org.apache.sis.internal.feature.AttributeConvention;
 import org.apache.sis.internal.storage.query.SimpleQuery;
 import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreReferencingException;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.collection.Containers;
@@ -255,16 +260,6 @@ public class JoinFeatureSet extends AbstractFeatureSet {
     }
 
     /**
-     * Returns the name of the join operation resulting feature type.
-     *
-     * @return resulting feature type name.
-     */
-    @Override
-    public GenericName getIdentifier() {
-        return type.getName();
-    }
-
-    /**
      * Creates a minimal {@code properties} map for feature type or property type constructors.
      * This minimalist map contain only the mandatory entry, which is the name.
      */
@@ -294,15 +289,38 @@ public class JoinFeatureSet extends AbstractFeatureSet {
     }
 
     /**
-     * Returns {@code null} since computing an envelope would be costly for this set.
+     * Returns the union of the envelope on both sides, or {@code null} if none.
+     * The coordinate reference system is the CRS of the "main" side.
      *
-     * @return always {@code null} in default implementation.
-     *
-     * @todo Revisit the method contract by allowing the envelope to be only an estimation, potentially larger.
+     * @return union of envelopes of both sides, or {@code null}.
+     * @throws DataStoreException if an error occurred while computing the envelope.
      */
     @Override
-    public Envelope getEnvelope() {
-        return null;
+    public Envelope getEnvelope() throws DataStoreException {
+        final List<Envelope> envelopes = new ArrayList<>();
+        getEnvelopes(envelopes);
+        try {
+            return Envelopes.union(envelopes.toArray(new Envelope[envelopes.size()]));
+        } catch (TransformException e) {
+            throw new DataStoreReferencingException(e);
+        }
+    }
+
+    /**
+     * Adds the envelopes of the two joined feature set in the given list. If some of the feature sets
+     * are themselves joined feature sets, then this method traverses them recursively. We compute the
+     * union of all envelopes at once after we got all envelopes.
+     */
+    private void getEnvelopes(final List<Envelope> addTo) throws DataStoreException {
+        boolean side = swapSides;
+        do {
+            final FeatureSet f = side ? right : left;
+            if (f instanceof JoinFeatureSet) {
+                ((JoinFeatureSet) f).getEnvelopes(addTo);
+            } else {
+                addTo.add(f.getEnvelope());
+            }
+        } while ((side = !side) != swapSides);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
index 6757d43..32a911d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
@@ -18,7 +18,6 @@ package org.apache.sis.internal.storage;
 
 import java.util.Collection;
 import java.util.stream.Stream;
-import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.util.ArgumentChecks;
@@ -73,16 +72,6 @@ public final class MemoryFeatureSet extends AbstractFeatureSet {
     }
 
     /**
-     * Returns the name of the feature type.
-     *
-     * @return feature type name.
-     */
-    @Override
-    public GenericName getIdentifier() {
-        return type.getName();
-    }
-
-    /**
      * Returns the type common to all feature instances in this set.
      *
      * @return a description of properties that are common to all features in this dataset.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
index 2b08ef4..2557a06 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
@@ -21,7 +21,11 @@ import java.util.stream.Stream;
 import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
 import org.opengis.util.GenericName;
+import org.opengis.geometry.Envelope;
 import org.opengis.metadata.Metadata;
+import org.opengis.metadata.extent.Extent;
+import org.opengis.metadata.extent.GeographicExtent;
+import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.identification.Identification;
 import org.opengis.metadata.identification.DataIdentification;
 import org.apache.sis.util.Static;
@@ -33,6 +37,7 @@ import org.apache.sis.storage.DataStoreProvider;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.WritableFeatureSet;
 import org.apache.sis.storage.UnsupportedStorageException;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.util.Citations;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
@@ -92,7 +97,7 @@ public final class StoreUtilities extends Static {
      * @param  unicode   whether to restrict to valid Unicode identifiers.
      * @return a data identifier, or {@code null} if none.
      *
-     * @see AbstractResource#identifier(Metadata)
+     * @see DataStore#getIdentifier()
      * @see Citations#removeIgnorableCharacters(String)
      */
     private static String getAnyIdentifier(final Metadata metadata, final boolean unicode) {
@@ -141,6 +146,36 @@ public final class StoreUtilities extends Static {
     }
 
     /**
+     * Returns the spatio-temporal envelope of the given metadata.
+     * This method computes the union of all {@link GeographicBoundingBox} in the metadata, assuming the
+     * {@linkplain org.apache.sis.referencing.CommonCRS#defaultGeographic() default geographic CRS}
+     * (usually WGS 84).
+     *
+     * @param  metadata  the metadata from which to compute the envelope, or {@code null}.
+     * @return the spatio-temporal extent, or {@code null} if none.
+     */
+    public static Envelope getEnvelope(final Metadata metadata) {
+        GeneralEnvelope bounds = null;
+        if (metadata != null) {
+            for (final Identification identification : metadata.getIdentificationInfo()) {
+                for (final Extent extent : identification.getExtents()) {
+                    for (final GeographicExtent ge : extent.getGeographicElements()) {
+                        if (ge instanceof GeographicBoundingBox) {
+                            final GeneralEnvelope env = new GeneralEnvelope((GeographicBoundingBox) ge);
+                            if (bounds == null) {
+                                bounds = env;
+                            } else {
+                                bounds.add(env);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return bounds;
+    }
+
+    /**
      * Returns the most specific interface implemented by the given class.
      * For indicative purpose only, as this method has arbitrary behavior if more than one leaf is found.
      *
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
index cc65820..9fe1f5f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
@@ -19,7 +19,6 @@ package org.apache.sis.internal.storage.query;
 import java.util.List;
 import java.util.stream.Stream;
 import org.opengis.util.GenericName;
-import org.opengis.geometry.Envelope;
 import org.apache.sis.internal.feature.FeatureUtilities;
 import org.apache.sis.internal.storage.AbstractFeatureSet;
 import org.apache.sis.storage.DataStoreException;
@@ -80,14 +79,6 @@ final class FeatureSubset extends AbstractFeatureSet {
     }
 
     /**
-     * Returns {@code null} since computing the envelope would be costly.
-     */
-    @Override
-    public Envelope getEnvelope() {
-        return null;
-    }
-
-    /**
      * Returns a description of properties that are common to all features in this dataset.
      */
     @Override
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataSet.java
index b633835..d9ef1fa 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataSet.java
@@ -49,7 +49,7 @@ import org.opengis.geometry.Envelope;
 public interface DataSet extends Resource {
     /**
      * Returns the spatio-temporal extent of this resource in its most natural coordinate reference system.
-     * The following relationship to {@linkplain #getMetadata()} should hold:
+     * The following relationship to {@linkplain #getMetadata()} should hold (departures may exist):
      *
      * <ul>
      *   <li>The envelope should be contained in the union of all geographic, vertical or temporal extents
@@ -67,6 +67,9 @@ public interface DataSet extends Resource {
      * If this resource uses many different CRS with none of them covering all data, then the envelope should use a
      * global system (typically a {@linkplain org.apache.sis.referencing.crs.DefaultGeocentricCRS geographic CRS}).
      *
+     * <p>The returned envelope is not necessarily the smallest bounding box encompassing all data.
+     * If the smallest envelope is too costly to compute, this method may conservatively return a larger envelope.</p>
+     *
      * @return the spatio-temporal resource extent. Should not be {@code null} (but may happen if too costly to compute).
      * @throws DataStoreException if an error occurred while reading or computing the envelope.
      */
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 510ea56..0889df2 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@ -23,14 +23,17 @@ import java.util.NoSuchElementException;
 import org.opengis.util.ScopedName;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.Metadata;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.identification.Identification;
 import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.WarningListener;
 import org.apache.sis.util.logging.WarningListeners;
-import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.referencing.NamedIdentifier;
 
 
 /**
@@ -292,7 +295,31 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable {
      */
     @Override
     public GenericName getIdentifier() throws DataStoreException {
-        return AbstractResource.identifier(getMetadata());
+        final Metadata metadata = getMetadata();
+        if (metadata != null) {
+            Citation citation = null;
+            for (final Identification id : metadata.getIdentificationInfo()) {
+                final Citation c = id.getCitation();
+                if (c != null) {
+                    if (citation != null && citation != c) return null;                 // Ambiguity.
+                    citation = c;
+                }
+            }
+            if (citation != null) {
+                Identifier first = null;
+                for (final Identifier c : citation.getIdentifiers()) {
+                    if (c instanceof GenericName) {
+                        return (GenericName) c;
+                    } else if (first == null) {
+                        first = c;
+                    }
+                }
+                if (first != null) {
+                    return new NamedIdentifier(first);
+                }
+            }
+        }
+        return null;
     }
 
     /**
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
index 7c8db04..4f0602f 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
@@ -32,7 +32,7 @@ import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.ConcurrentReadException;
 import org.apache.sis.storage.IllegalNameException;
 import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.storage.AbstractResource;
+import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.internal.storage.xml.stream.StaxDataStore;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.ArgumentChecks;
@@ -177,7 +177,7 @@ public final class Store extends StaxDataStore implements FeatureSet {
      */
     @Override
     public Envelope getEnvelope() throws DataStoreException {
-        return AbstractResource.envelope(getMetadata());
+        return StoreUtilities.getEnvelope(getMetadata());
     }
 
     /**


Mime
View raw message